Аппаратное обеспечение персонального компьютера

         

Порт 08h


Этот порт используется при записи в качестве управляющего регистра и при чтении как регистр состояния.

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

Поле

Описание

0

1 - использование режима память-память;

0 - обычный режим работы

1

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

2

1 - запрет работы DMA;

0 - разрешение работы DMA

3

1 - использование сжатия во времени, если установлен бит обычного режима работы;

0 - обычный режим работы

4

1 – циклическое изменение приоритетов;

0 - фиксированные приоритеты

5

1 - удлиненный цикл записи;

0 - нормальный цикл записи

6

1 - для сигнала запроса на DMA используется низкий уровень DREQ;

0 – для этого сигнала используется высокий уровень

7

1 - для сигнала подтверждения DMA DACK используется высокий уровень;

0 - для этого сигнала используется низкий уровень

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

При чтении из порта 08h программа получает слово состояния контроллера DMA:

Поле

Описание

0-3

Биты 0-3 устанавливаются при достижении счетчиками каналов 0-3 конечных значений;

4-7

Биты 4-7 установлены, если имеется разрешение на DMA, соответственно, каналов 0-3



Порт 09h


Регистр запроса. Предназначен для организации программного (а не аппаратного) запроса на DMA. Для использования программного запроса канал должен быть запрограммирован в режиме передачи блоков данных.

Формат регистра:

Поле

Описание

0-1

Номер канала:

00 – канал 0;

01 – канал 1

10 – канал 2;

11 – канал 3

2

0 – установить запрос;

1 – сбросить запрос

3-7

Не используются



Порт 37Ah


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

Поле

Описание

0

STROBE

Cтроб данных, принимает значение 1 при выводе байта, подключен к 1 контакту разъема параллельного адаптера

1

AUTO LineFeed

Автоматический перевод строки после символа возврата каретки CR, контакт 14

2

INIT

Сброс принтера, активный уровень - 0, контакт 16

3

SLCT IN

Выбор принтера для работы, контакт 17

4

IRQ Enable

Разрешение прерывания от принтера. Если прерывания от принтера разрешены, они вырабатываются когда сигнал готовности принтера ACK (контакт 10) принимает уровень логического 0

5-7

Равно 0



Порт 378h


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



Порт 379h


Порт состояния принтера, доступен только для чтения:

Поле

Описание

0-2

Равно 0

3

ERROR

Сигнал ошибки, активный уровень - 0, контакт 15

4

SLCT

Принтер выбран, контакт 13

5

PE

Конец бумаги, контакт 12

6

ACK

Готовность принтера, активный уровень - 0, контакт 10

7

BUSY

0 - принтер занят, находится в состоянии offline или произошла ошибка, контакт 11



Порты 0C0h - 0DFh


Эти регистры содержат базовые адреса и счетчики передаваемых данных каналов 4-7. Их назначение приводится ниже:

Порт

Операция

Назначение

0C0h

Запись:

Базовый адрес канала 4

Чтение:

Текущий адрес

0C2h

Запись:

Счетчик канала 4

Чтение:

Текущий адрес

0C4h

Запись:

Базовый адрес канала 5

Чтение:

Текущий адрес

0C6h

Запись:

Счетчик канала 5

Чтение:

Текущий адрес

0C8h

Запись:

Базовый адрес канала 6

Чтение:

Текущий адрес

0CAh

Запись:

Счетчик канала 6

Чтение:

Текущий адрес

0CCh

Запись:

Базовый адрес канала 7

Чтение:

Текущий адрес

0CEh

Запись:

Счетчик канала 7

Чтение:

Текущий адрес



Порты 0D0h-0DFh


Это управляющие порты и порты состояния второй микросхемы 8237A-5. По формату и назначению они соответствуют рассмотренным ранее для контроллера DMA компьютеров IBM PC/XT:

Порт

Назначение

0D0h

Управляющий регистр, регистр состояния

0D2h

Регистр запроса

0D4h

Регистр маски

0D6h

Регистр режима

0D8h

Сброс триггера байтов

0DAh

Сброс контроллера

0DCh

Сброс регистра маски

0DEh

Маскирование и размаскирование каналов



Порты 00h - 07h


Эти регистры содержат базовые адреса и счетчики передаваемых данных каналов 0 - 3. Их назначение приводится в следующей таблице:

Порт

Операция

Назначение

00h

Запись:

Базовый адрес канала 0

Чтение:

Текущий адрес

01h

Запись:

Счетчик канала 0

Чтение:

Текущий адрес

02h

Запись:

Базовый адрес канала 1

Чтение:

Текущий адрес

03h

Запись:

Счетчик канала 1

Чтение:

Текущий адрес

04h

Запись:

Базовый адрес канала 2

Чтение:

Текущий адрес

05h

Запись:

Счетчик канала 2

Чтение:

Текущий адрес

06h

Запись:

Базовый адрес канала 3

Чтение:

Текущий адрес

07h

Запись:

Счетчик канала 3

Чтение:

Текущий адрес



Порты 81h-8Fh


Это порты регистров страниц.

Для работы с памятью контроллер прямого доступа IBM PC/XT использует 20-разрядные физические адреса. Шестнадцать младших битов адреса необходимо записать в регистр базового адреса канала. Четыре старших бита (биты 16-19) должны быть записаны в соответствующие порты регистров страниц.

При инициализации регистров базового адреса и регистра страниц необходимо следить за тем, чтобы в процессе передачи данных не происходил переход за границу 64 Кбайт.

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

Порт

Описание

81h

Регистр страниц канала 2

82h

Регистр страниц канала 3

83h

Регистр страниц канала 1



Порты асинхронного адаптера


На этапе инициализации системы BIOS тестирует имеющиеся асинхронные последовательные адаптеры и инициализирует первые два. Их базовые адреса располагаются в области данных BIOS начиная с адреса 0000:0400h.

Первый адаптер COM1 имеет базовый адрес 3F8h и занимает диапазон адресов от 3F8h до 3FFh, второй адаптер COM2 имеет базовый адрес 2F8h и занимает адреса 2F8h...2FFh.

Асинхронные адаптеры могут вырабатывать прерывания:

COM1 - IRQ4 (соответствует INT 0Ch);

COM2 - IRQ3 (соответствует INT 0Bh)

Заметим, что в некоторых компьютерах вы можете изменить базовые адреса адаптеров и номера прерываний с помощью программы BIOS Setup.



Порты для работы с клавиатурой


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



Порты параллельного адаптера


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

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



Последовательность действий


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

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

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

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

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

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

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

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



Последовательный порт компьютера PCjr


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



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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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


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

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

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

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

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



Прием байта


Функция 02h предназначена для приема байта:

Регистры на входе:

AH = 02h;

DX = номер порта адаптера: 0 - COM1, 1 - COM2;

Регистры на выходе:

AL = принятый байт;

AH = состояние порта асинхронного адаптера. Если бит 7 регистра AH установлен, произошла ошибка



Прием данных


Аналогично тому как это делается при передаче данных, перед вводом символа из порта приемника 3F8h необходимо убедиться в том, что бит 0 порта 3FDh установлен. Это означает, что символ принят из линии и находится в буферном регистре приемника.



Приложение RTFPAD


В 22 томе «Библиотеки системного программиста», который называется «Операционная система Windows 95 для программиста» мы привели исходные тексты приложения RTFPAD. Это приложение представляет собой текстовый редактор, способный работать с документами в формате RTF. Такой документ может содержать шрифтовое оформление.

Для того чтобы продемонстрировать обработку сообщений от мыши Microsoft IntelliMouse, мы немного изменили приложение RTFPAD. В листинге 3.7 вы найдете исходный текст измененных функций WinMain и WndProc.

Обратите внимание, что при инициализации приложения мы определяем версию операционной системы с тем чтобы приложение могло работать как в среде Microsoft Windows 95, так и в среде Microsoft NT версии 4.0.

Листинг 3.7 (сокращенный). Файл rtfpad\rtfpad.с

// =====================================================

// Редактор текста RTFPAD, способный работать

// с мышью Microsoft IntelliMouse

//

// (C) Фролов А.В, 1996, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

// Это определение нужно для того, чтобы при компилляции

// файла winuser.h были подключены определения

// идентификаторов SPI_GETWHEELSCROLLLINES

// и WM_MOUSEWHEEL

#define _WIN32_WINNT 0x0400

// Еще один способ определения этих же идентификаторов

//#ifndef SPI_GETWHEELSCROLLLINES

//#define SPI_GETWHEELSCROLLLINES   104

//#endif

//#ifndef WM_MOUSEWHEEL

//#define WM_MOUSEWHEEL WM_MOUSELAST+1

//#endif

#define STRICT

#include <windows.h>

#include <windowsx.h>

#include <commctrl.h>

#include <richedit.h>

// Необходимо для определения значения константы

// UINT_MAX, которая используется в файле zmouse.h

#include <limits.h>

#include "resource.h"

#include "afxres.h"

// Файл определений для IntelliMouse

#include "zmouse.h"


#include "rtfpad.h"

// Код сообщения MSH_MOUSEEHEEL

UINT uMSH_MOUSEEHEEL   = 0;

// Код сообщения MSH_SUPPORT

UINT uMSH_SUPPORT      = 0;

// Код сообщения MSH_SCROLL_LINES

UINT uMSH_SCROLL_LINES = 0;

// Идентификатор окна для посылки сообщений

// приложению MSWheel

HWND hwndMSHWheel  = NULL;

// Флаг наличия мыши Microsoft IntelliMouse

BOOL fWheel        = FALSE;

// Количество строк свертки

UINT uiScrollLines = 3;

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

// операционной системы

OSVERSIONINFO osv;

HINSTANCE hInst;

char szAppName[]  = "RtfEditApp";

char szAppTitle[] = "Rich Text Editor RtfPad";

HWND hwndEdit;

HINSTANCE hRTFLib;

// -----------------------------------------------------

// Функция WinMain

// -----------------------------------------------------

int APIENTRY

WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

        LPSTR lpCmdLine, int nCmdShow)

{

  WNDCLASSEX wc;

  HWND hWnd;

  MSG msg;

 

  hInst = hInstance;

  hWnd = FindWindow(szAppName, NULL);

  if(hWnd)

  {

    if(IsIconic(hWnd))

         ShowWindow(hWnd, SW_RESTORE);

         SetForegroundWindow(hWnd);

    return FALSE;

  }

  // Определяем версию операционной системы

  memset(&osv, 0, sizeof(OSVERSIONINFO));

  osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

  GetVersionEx(&osv);

  // Для Windows 95 и Windows NT версии 3.51 выполняем

  // регистрацию сообщений

  if( (osv.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)

     ((osv.dwPlatformId == VER_PLATFORM_WIN32_NT) &&

      (osv.dwMajorVersion < 4)))

  {

    // Регистрируем сообщение MSH_WHEELSUPPORT

    uMSH_SUPPORT =

      RegisterWindowMessage(MSH_WHEELSUPPORT);

   

    // Определяем наличие мыши Microsoft IntelliMouse

    hwndMSHWheel =

      FindWindow(MSH_WHEELMODULE_CLASS,

      MSH_WHEELMODULE_TITLE);

   

    if(uMSH_SUPPORT != 0 && hwndMSHWheel != 0)



    {

      fWheel =

        (BOOL)SendMessage(hwndMSHWheel, uMSH_SUPPORT, 0, 0);

    }

    if(!fWheel)

    {

      MessageBox(NULL, "MS Wheel not supported",

        "Error message", MB_OK);

    }

 

    // Регистрируем сообщение MSH_MOUSEWHEEL

    uMSH_MOUSEEHEEL = RegisterWindowMessage(MSH_MOUSEWHEEL);

    if(!uMSH_MOUSEEHEEL)

    {

      MessageBox(NULL, "Error: RegisterWindowMessage",

       "Error message", MB_OK);

      return FALSE;

    }

    // Регистрируем сообщение MSH_SCROLL_LINES

    uMSH_SCROLL_LINES =

      RegisterWindowMessage(MSH_SCROLL_LINES);

   

    // Определяем количество строк свертки

    if(uMSH_SCROLL_LINES != 0 && hwndMSHWheel != 0)

    {

      uiScrollLines =

        (BOOL)SendMessage(hwndMSHWheel,

        uMSH_SCROLL_LINES, 0, 0);

    }

  }

  // Для Windows NT версии 4.0 применяем другую методику

  else

  {

    // Проверяем наличие мыши Microsoft IntelliPoint

    if(!GetSystemMetrics(SM_MOUSEWHEELPRESENT))

    {

      MessageBox(NULL,

        "Microsoft IntelliMouse not found",

        "Error message", MB_OK);

    }

    // Определяем количество строк свертки

    SystemParametersInfo(SPI_GETWHEELSCROLLLINES,

      0, &uiScrollLines, 0);

  }

  hRTFLib = LoadLibrary("RICHED32.DLL");

  if(!hRTFLib)

    return FALSE;

  memset(&wc, 0, sizeof(wc));

  wc.cbSize = sizeof(WNDCLASSEX);

  wc.hIconSm = LoadImage(hInst,

    MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0);

  wc.style = 0;

  wc.lpfnWndProc = (WNDPROC)WndProc;

  wc.cbClsExtra  = 0;

  wc.cbWndExtra  = 0;

  wc.hInstance = hInst;

  wc.hIcon = LoadImage(hInst,

    MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);

  wc.hCursor = LoadCursor(NULL, IDC_ARROW);

  wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);

  wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU);

  wc.lpszClassName = szAppName;

  if(!RegisterClassEx(&wc))



    if(!RegisterClass((LPWNDCLASS)&wc.style))

         return FALSE;

   

  hWnd = CreateWindow(szAppName, szAppTitle,

    WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0,

    CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL);

  if(!hWnd) return(FALSE);

  ShowWindow(hWnd, nCmdShow);

  UpdateWindow(hWnd);

  while(GetMessage(&msg, NULL, 0, 0))

  {

    TranslateMessage(&msg);

    DispatchMessage(&msg);

  }

  return msg.wParam;

}

// -----------------------------------------------------

// Функция WndProc

// -----------------------------------------------------

LRESULT WINAPI

WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

  // Изменение положения колеса

  short zDelta;

  switch(msg)

  {

    HANDLE_MSG(hWnd, WM_CREATE,     WndProc_OnCreate);

    HANDLE_MSG(hWnd, WM_DESTROY,    WndProc_OnDestroy);

    HANDLE_MSG(hWnd, WM_COMMAND,    WndProc_OnCommand);

    HANDLE_MSG(hWnd, WM_SIZE,       WndProc_OnSize);

    HANDLE_MSG(hWnd, WM_SETFOCUS,   WndProc_OnSetFocus);

         default:

    {

      if(msg == WM_MOUSEWHEEL)

      {

        zDelta = (short)HIWORD(wParam);

        if(zDelta < 0)

          if(uiScrollLines != WHEEL_PAGESCROLL)

            SendMessage(hwndEdit, EM_LINESCROLL, 0, 

              uiScrollLines);

       

          else

            SendMessage(hwndEdit, EM_SCROLL,

              (WPARAM)(INT)SB_PAGEDOWN, 0);

        else

         

          if(uiScrollLines != WHEEL_PAGESCROLL)

            SendMessage(hwndEdit, EM_LINESCROLL, 0,

              -(LPARAM)uiScrollLines);

         

          else

            SendMessage(hwndEdit, EM_SCROLL,

              (WPARAM)(INT)SB_PAGEUP, 0);

        return 0L;

      }

      else if(msg == uMSH_MOUSEEHEEL)

      {

        zDelta = (short)wParam;

        if(zDelta < 0)

          if(uiScrollLines != WHEEL_PAGESCROLL)

            SendMessage(hwndEdit, EM_LINESCROLL, 0,

              uiScrollLines);

      

          else

            SendMessage(hwndEdit, EM_SCROLL,

              (WPARAM)(INT)SB_PAGEDOWN, 0);

        else

          

          if(uiScrollLines != WHEEL_PAGESCROLL)

            SendMessage(hwndEdit, EM_LINESCROLL, 0,

              -(LPARAM)uiScrollLines);

         

          else

            SendMessage(hwndEdit, EM_SCROLL,

              (WPARAM)(INT)SB_PAGEUP, 0);

       

        return 0L;

      }

      return(DefWindowProc(hWnd, msg, wParam, lParam));

    }

  }

}

. . .

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


Примеры программ


Приведем исходные тексты нескольких программ, демонстрирующих вызов функций программного интерфейса драйвера HIMEM.SYS. эти программы предназначены для работы в среде MS-DOS.



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


Регистры на входе:

AH = 04h

Регистры на выходе:

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

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

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

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

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



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


Регистры на входе:

AH = 02h

Регистры на выходе:

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

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

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

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



Программа BIOSINFO


Программа BIOSINFO получает и отображает на консоли дату изготовления версии BIOS, а также содержимое таблицы конфигурации, адрес которой определяется с помощью функции C0h прерывания BIOS INT15h:

BIOSINFO (C)A. Frolov, 1997

BIOS data:        04/18/97

BIOSINFO address: 0212:0190

BIOSINFO Size:    8

Model:            FC

SubModel:         1

BIOS Revision:    0

Hardvare Cfg:     70

Reserved1:        00

Reserved2:        00

Hardware configuration

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

Second IRQ Controller 8259

Real Time Clock

Used function 4Fh INT 15h

ISA Bus installed

Исходный текст программы представлен в листинге 1.2.

Листинг 1.2. Файл biosinfo\biosinfo.c

// =====================================================

// Получение информации о BIOS

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <dos.h>

// Структура области данных с информацией о BIOS

typedef struct _BIOSINFO

{

  int nSize;                   // размер структуры

  unsigned char bModel;        // код модели компьютера

  unsigned char bSubModel;     // дополнительный код модели

  unsigned char bBIOSRevision; // номер изменений

                               //  версии BIOS

  unsigned char bHardwareCfg;  // конфигурация аппаратуры

  int reserved1;               // зарезервировано

  int reserved2;               // зарезервировано

} BIOSINFO;

int main(void)

{

  union REGS  rg;

  struct SREGS srg;

  int   i;

  BIOSINFO far *lpbi;

  void far* lp;

  unsigned char bHdwCfg;

  printf("\nBIOSINFO (C)A. Frolov, 1997");

  // Конструируем указатель на дату изготовления

  // BIOS. Эта дата записана в ПЗУ по адресу F000h:FFF5h

  _FP_SEG(lp) = 0xf000;

  _FP_OFF(lp) = 0xfff5;

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


  printf("\n\nBIOS data:        ");

  for(i=0; i<8; i++)

    putch(*((char far *)lp + i));

  // Вызываем функцию C0h для получения адреса

  // таблицы конфигурации компьютера.

  rg.h.ah = 0xc0;

  int86x(0x15, &rg, &rg, &srg);

  // Если в BIOS нет данной функции,

  // читаем код модели компьютера

  // из ПЗУ по адресу F000h:FFFEh

  if(rg.x.cflag == 1)  

  {

    printf("\nFunction C0h INT 15h not supported\n");

    // Конструируем указатель на код модели   

    _FP_SEG(lp) = 0xf000;   

    _FP_OFF(lp) = 0xfffe;

    // Выводим код модели компьютера на экран

    printf("\nModel:             %02.2X",

      (unsigned char)(*(char far *)lp));

    return(-1);

  }

    

  // Конструируем укзатель на таблицу

  // информации о BIOS

  _FP_SEG(lpbi) = srg.es;

  _FP_OFF(lpbi) = rg.x.bx;

  // Выводим на экран содержимое таблицы

  printf("\nBIOSINFO address: %Fp"

         "\nBIOSINFO Size:    %d"

         "\nModel:            %02.2X"

         "\nSubModel:         %d"

         "\nBIOS Revision:    %d"

         "\nHardvare Cfg:     %02.2X"

         "\nReserved1:        %02.2X"

         "\nReserved2:        %02.2X",

   lpbi, lpbi->nSize, lpbi->bModel, lpbi->bSubModel,

   lpbi->bBIOSRevision, lpbi->bHardwareCfg,

   lpbi->reserved1, lpbi->reserved2);

  

  // Определяем конфигурацию компьютера

  printf("\n\nHardware configuration"

           "\n----------------------");

  // Запоминаем байт конфигурации

  bHdwCfg = lpbi->bHardwareCfg;

  // Расшифровываем байт конфигурации

  if(bHdwCfg & 0x80)

    printf("\nDMA Channel 3");

 

  if(bHdwCfg & 0x40)

    printf("\nSecond IRQ Controller 8259");

  if(bHdwCfg & 0x20)

    printf("\nReal Time Clock");

  if(bHdwCfg & 0x10)

    printf("\nUsed function 4Fh INT 15h");

  if(bHdwCfg & 0x8)

    printf("\nBIOS event wait supported");

  if(bHdwCfg & 0x4)

    printf("\nExtended BIOS data used");

  if(bHdwCfg & 0x2)

    printf("\nMicro Channel Bus");

  if(!(bHdwCfg & 0x2))

    printf("\nISA Bus installed\n");

 

  getch();

  return 0;

}


Программа CALLHMA


Приведем текст программы, составленная на языке программирования Си, которая вызывает функции драйвера расширенной памяти. Эта программа будет работать только в моделях памяти Small и Compact. Для других моделей памяти требуется изменить строки интерфейсного модуля hma.asm, в которых передаваемые функциям параметры извлекаются из стека и тип процедур:

Аргументы

Small, Compact

Large, Huge

Первый аргумент

[bp+4]

 [bp+6]

Второй аргумент

[bp+6]

 [bp+8]

Текст программы CALLHMA вы найдете в листинге 11.2, а текст интерфейсного модуля - в листинге 11.3.

Листинг 11.2. Файл callhma\callhma.c

// =====================================================

// Работа с драйвером HIMEM.SYS

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <dos.h>

#include <string.h>

struct   XMM_Move

{

  unsigned long  Length;

  unsigned short SourceHandle;

  unsigned long  SourceOffset;

  unsigned short DestHandle;

  unsigned long  DestOffset;

};

extern long XMM_Installed(void);

extern long XMM_Version(void);

extern long XMM_RequestHMA(unsigned);

extern long XMM_ReleaseHMA(void);

extern long XMM_GlobalEnableA20(void);

extern long XMM_GlobalDisableA20(void);

extern long XMM_EnableA20(void);

extern long XMM_DisableA20(void);

extern long XMM_QueryA20(void);

extern long XMM_QueryLargestFree(void);

extern long XMM_QueryTotalFree(void);

extern long XMM_AllocateExtended(unsigned);

extern long XMM_FreeExtended(unsigned);

extern long XMM_MoveExtended(struct XMM_Move *);

extern long XMM_LockExtended(unsigned);

extern long XMM_UnLockExtended(unsigned);

extern long XMM_GetHandleLength(unsigned);

extern long XMM_GetHandleInfo(unsigned);

extern long XMM_ReallocateExtended(unsigned, unsigned);


extern long XMM_RequestUMB(unsigned);

extern long XMM_ReleaseUMB(unsigned);

void error(char *msg, long rc);

int main(void)

{

  long ver, rc, handle;

  static char testmsg[] = "Тестовое сообщение";

  char buf[80];

  char far *ptr;

  int i;

  struct XMM_Move move_d;

  // Проверяем, установлен ли драйвер HIMEM.SYS,

  // если установлен, выводим его версию

  if (XMM_Installed())

  {

    printf("\nHIMEM.SYS installed");

    ver = XMM_Version();

    printf("\nver XMM: %4X,%4x",

      (short)ver, (short)(ver >> 16));

  }

  else

  {

    printf("\nHIMEM.SYS not found");

    exit(-1);

  }

  // Запрашиваем управление областью HMA

  rc = XMM_RequestHMA(0xffff);

  if(rc)

    error("Request HMA error",rc);

  else

  {

    // Открываем линию A20

    rc = XMM_GlobalEnableA20();

    if(rc)

      error("Open A20 error",rc);

    // Копируем тестовое сообщение сначала из

    // стандартной памяти в область HMA,

    // затем обратно в стандартную память

    FP_SEG(ptr) = 0xffff;

    FP_OFF(ptr) = 0x0010;

    for(i=0; testmsg[i] != 0; i++)

      ptr[i] = testmsg[i];

    for(i=0; ptr[i] != 0; i++)

      buf[i] = ptr[i];

   

    buf[i] = 0;

    // Выводим сообщение для проверки

    printf("\n%s",buf);

    // Закрываем линию A20 и отдаем системе область HMA

    rc = XMM_GlobalDisableA20();

    if(rc)

      error("Close A20 eror",rc);

    rc = XMM_ReleaseHMA();

    if(rc)

      error("Free HMA error",rc);

  }

  // Получаем блок EMB размером в 1 Кбайт

  handle = XMM_AllocateExtended(1);

  if(handle < 0)

    error("Request XMB error",handle);

  // Копируем тестовое сообщение сначала из

  // стандартной памяти в блок EMB,

  // затем обратно в стандартную память

  move_d.Length = strlen(testmsg) + 1;

  move_d.SourceHandle = 0;



  (char far*)move_d.SourceOffset = (char far*)testmsg;

  move_d.DestHandle = (short)handle;

  move_d.DestOffset = 0L;

  rc = XMM_MoveExtended(&move_d);

  if(rc < 0)

    error("Copy in EMB error",rc);

  move_d.Length = strlen(testmsg) + 1;

  move_d.DestHandle = 0;

  (char far*)move_d.DestOffset = (char far*)buf;

  move_d.SourceHandle = (short)handle;

  move_d.SourceOffset = 0L;

  rc = XMM_MoveExtended(&move_d);

  if(rc < 0)

    error("Copy from EMB error",rc);

  // Выводим сообщение для проверки

  printf("\n%s",buf);

  // Освобождаем блок EMB

  rc = XMM_FreeExtended((unsigned)handle);

  if(rc)

    error("Free XMB error",rc);

  return 0;

}

// Функция для вывода сообщения об ошибке

// и кода ошибки

void error(char *msg, long rc)

{

  rc = (unsigned char)(rc >> 24) ;

    printf("\n%s, error: %02.2X\n",

      msg, (unsigned char)rc);

  exit(-1);

}

Листинг 11.3. Файл callhma\hma.asm

; =====================================================

; Это интерфейсный модуль для вызова функций

; XMS из Си. Текст программы рассчитан на

; модель памяти Small

;

; (C) A. Frolov, 1997

;

; E-mail: frolov@glas.apc.org

; WWW:    http://www.glasnet.ru/~frolov

;            or

;         http://www.dials.ccas.ru/frolov

; =====================================================

  .model small

  .DATA

; В этом месте будет храниться адрес

; управляющей функции XMM

XMM_Control  dd   ?

          .CODE

; Макроопределения для выполнения соглашения об

; использовании регистров в процедурах Си

c_begin macro

  push bp

  mov  bp,sp

  push si

  push di

  endm

c_end   macro

  pop  di

  pop  si

  mov  sp,bp

  pop  bp

  ret

  endm

; Все процедуры должны быть public

  public _XMM_Installed

  public _XMM_Version

  public _XMM_RequestHMA

  public _XMM_ReleaseHMA



  public _XMM_GlobalEnableA20

  public _XMM_GlobalDisableA20

  public _XMM_EnableA20

  public _XMM_DisableA20

  public _XMM_QueryA20

  public _XMM_QueryLargestFree

  public _XMM_QueryTotalFree

  public _XMM_AllocateExtended

  public _XMM_FreeExtended

  public _XMM_MoveExtended

  public _XMM_LockExtended

  public _XMM_UnLockExtended

  public _XMM_GetHandleLength

  public _XMM_GetHandleInfo

  public _XMM_ReallocateExtended

  public _XMM_RequestUMB

  public _XMM_ReleaseUMB

;**

;.Name         _XMM_Installed

;.Title        Получение адреса управляющей функции

;

;.Descr        Эта функция проверяет наличие драйвера

;              HIMEM.SYS и в случае его присуствия

;              запоминает адрес управляющей функции.

;

;.Proto        unsigned XMM_Installed(void);

;

;.Params       Не используются

;

;.Return       0 - драйвер HIMEM.SYS не установлен;

;              1 - драйвер HIMEM.SYS установлен.

;

;**

_XMM_Installed proc near

          c_begin

          mov  ax, 4300h

          int  2fh

          cmp  al, 80h

          jne  NotInstalled

          mov  ax, 4310h

          int  2fh

          mov  word ptr [XMM_Control], bx

          mov  word ptr [XMM_Control+2], es

          mov  ax,1

          jmp  Installed

NotInstalled:

          mov  ax, 0

Installed:

          c_end

_XMM_Installed endp

;**

;.Name         _XMM_Version

;.Title        Определение версии драйвера HIMEM.SYS

;

;.Descr        Эта функция определяет версию драйвера

;              HIMEM.SYS

;

;.Proto        long  XMM_Version(void);

;

;.Params       Не используются

;

;.Return       Номер версии в младших 16 битах,

;              номер изменений - в старших 16 битах

;              возвращаемого значения

;

;**

_XMM_Version proc near

          push si

          push di

          xor  ah, ah

          call [XMM_Control]

          mov  dx, bx

          pop  di

          pop  si

          ret



_XMM_Version endp

;**

;.Name         _XMM_RequestHMA

;.Title        Запросить область HMA

;

;.Descr        Эта функция пытается зарезервировать для

;              программы область HMA

;

;.Proto        long  XMM_RequestHMA(unsigned space);

;

;.Params       space - размер требуемой области для

;                      TSR-программы или драйвера,

;                      0xffff для прикладной программы;

;

;.Return       < 0 - область HMA не назначена программе,

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

;              0L  - область HMA назначена программе.

;

;**

_XMM_RequestHMA proc near

          c_begin

          mov  ah, 1

          mov  dx, [bp+4]

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @success

          mov  dh, bl

@success:

          c_end

_XMM_RequestHMA endp

;**

;.Name         _XMM_ReleaseHMA

;.Title        Освободить область HMA

;

;.Descr        Эта функция пытается освободить

;              область HMA

;

;.Proto        long  XMM_ReleaseHMA(void);

;

;.Params       Не используются

;

;.Return       < 0 - область HMA не освобождена,

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

;              0L - область HMA освобождена.

;

;**

_XMM_ReleaseHMA proc near

          c_begin

          mov  ah, 2

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @success1

          mov  dh, bl

@success1:

          c_end

_XMM_ReleaseHMA endp

;**

;.Name         _XMM_GlobalEnableA20

;.Title        Глобальное разрешение линии A20

;

;.Descr        Эта функция разрешает программе, получившей

;              доступ к области HMA использовать линию A20

;

;.Proto        long  XMM_GlobalEnableA20(void);

;

;.Params       Не используются

;

;.Return       < 0 - линия A20 не включена,

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

;              0L  - линия A20 включена.



;

;**

_XMM_GlobalEnableA20 proc near

          c_begin

          mov  ah, 3

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @success2

          mov  dh, bl

@success2:

          c_end

_XMM_GlobalEnableA20 endp

;**

;.Name         _XMM_GlobalDisableA20

;.Title        Глобальное запрещение линии A20

;

;.Descr        Эта функция запрещает программе, получившей

;              доступ к области HMA использовать линию A20

;

;.Proto        long  XMM_GlobalDisableA20(void);

;

;.Params       Не используются

;

;.Return       < 0 - линия A20 не выключена,

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

;              0L  - линия A20 выключена.

;

;**

_XMM_GlobalDisableA20 proc near

          c_begin

          mov  ah, 4

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @success3

          mov  dh, bl

@success3:

          c_end

_XMM_GlobalDisableA20 endp

;**

;.Name         _XMM_EnableA20

;.Title        Локальное разрешение линии A20

;

;.Descr        Эта функция разрешает программе управлять

;              областью расширенной памяти.

;

;.Proto        long  XMM_EnableA20(void);

;

;.Params       Не используются

;

;.Return       < 0 - линия A20 не включена,

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

;              0L  - линия A20 включена.

;

;**

_XMM_EnableA20 proc near

          c_begin

          mov  ah, 5

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @success4

          mov  dh, bl

@success4:

          c_end

_XMM_EnableA20 endp

;**

;.Name         _XMM_DisableA20

;.Title        Локальное запрещение линии A20

;

;.Descr        Эта функция запрещает программе управлять

;              областью расширенной памяти.

;

;.Proto        long  XMM_DisableA20(void);

;

;.Params       Не используются

;

;.Return       < 0 - линия A20 не выключена,



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

;              0L  - линия A20 выключена.

;

;**

_XMM_DisableA20 proc near

          c_begin

          mov  ah, 6

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @success5

          mov  dh, bl

@success5:

          c_end

_XMM_DisableA20 endp

;**

;.Name         _XMM_QueryA20

;.Title        Проверить состояние линии A20

;

;.Descr        Эта функция проверяет доступность

;              линии A20

;

;.Proto        long  XMM_QueryA20(void);

;

;.Params       Не используются

;

;.Return       < 0 - ошибка,

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

;              0L  - линия A20 выключена,

;              1L  - линия A20 включена.

;

;**

_XMM_QueryA20 proc near

          c_begin

          mov  ah, 7

          call [XMM_Control]

          xor  dx, dx

          or   ax, ax

          jnz  @success6

          mov  dh, bl

@success6:

          c_end

_XMM_QueryA20 endp

;**

;.Name         _XMM_QueryLargestFree

;.Title        Определить максимальный размер блока

;

;.Descr        Эта функция возвращает размер максимального

;              непрерывного блока расширенной памяти,

;              который доступен программе.

;

;.Proto        long  XMM_QueryLargestFree(void);

;

;.Params       Не используются

;

;.Return       < 0 - ошибка,

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

;              >= 0 - размер блока.

;

;**

_XMM_QueryLargestFree proc near

          c_begin

          mov  ah, 8

          call [XMM_Control]

          xor  dx, dx

          or   ax, ax

          jnz  @success7

          mov  dh, bl

@success7:

          c_end

_XMM_QueryLargestFree endp

;**

;.Name         _XMM_QueryTotalFree

;.Title        Определить размер расширенной памяти

;

;.Descr        Эта функция возвращает размер

;              всей имеющейся расширенной памяти.



;

;.Proto        long  XMM_QueryTotalFree(void);

;

;.Params       Не используются

;

;.Return       < 0 - ошибка,

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

;              >= 0 - размер расширенной памяти.

;

;**

_XMM_QueryTotalFree proc near

          c_begin

          mov  ah, 8

          call [XMM_Control]

          or   ax, ax

          mov  ax, dx

          mov  dx, 0

          jnz  @success8

          mov  dh, bl

@success8:

          c_end

_XMM_QueryTotalFree endp

;**

;.Name         _XMM_AllocateExtended

;.Title        Запросить блок расширенной памяти

;

;.Descr        Эта функция выделяет программе блок

;              расширенной памяти, в случае успеха

;              возвращает индекс полученного блока.

;

;.Proto        long XMM_AllocateExtended(unsigned space);

;

;.Params       space - размер требуемого блока памяти

;                      в килобайтах;

;

;.Return       < 0 - блок не распределен,

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

;              > 0L  - младший байт содержит индекс

;                      полученного блока памяти.

;

;**

_XMM_AllocateExtended proc near

          c_begin

          mov  ah, 9

          mov  dx,  [bp+4]

          call [XMM_Control]

          or   ax, ax

          mov  ax, dx

          mov  dx, 0

          jnz  @success9

          mov  dh, bl

@success9:

          c_end

_XMM_AllocateExtended endp

;**

;.Name         _XMM_FreeExtended

;.Title        Освободить блок расширенной памяти

;

;.Descr        Эта функция освобождает блок

;              расширенной памяти, полученный функцией

;              XMM_AllocateExtended().

;

;.Proto        long XMM_FreeExtended(unsigned handle);

;

;.Params       handle - индекс освобождаемого блока памяти;

;

;.Return       < 0 - блок не распределен,

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

;              0L  - блок освобожден.

;

;**



_XMM_FreeExtended proc near

          c_begin

          mov  ah, 0Ah

          mov  dx, [bp+4]

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @successA

          mov  dh, bl

@successA:

          c_end

_XMM_FreeExtended endp

;**

;.Name         _XMM_MoveExtended

;.Title        Копировать блок расширенной памяти

;

;.Descr        Эта функция копирует блок

;              расширенной памяти, используя структуру

;              struct XMM_Move:

;

;                 struct   XMM_Move {

;                    unsigned long  Length;

;                    unsigned short SourceHandle;

;                    unsigned long  SourceOffset;

;                    unsigned short DestHandle;

;                    unsigned long  DestOffset;

;                 };

;

;.Proto        long  XMM_MoveExtended(struct

;                       XMM_Move *move_descr);

;

;.Params       struct XMM_Move *move_descr -

;                 указатель на структуру, описывающую

;                 что, откуда и куда надо копировать.

;

;.Return       < 0 - ошибка при копировании,

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

;              0L  - блок скопирован успешно.

;

;**

_XMM_MoveExtended proc near

          c_begin

          mov  ah, 0Bh

          mov  si, [bp+4];

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @successB

          mov  dh, bl

@successB:

          c_end

_XMM_MoveExtended endp

;**

;.Name         _XMM_LockExtended

;.Title        Заблокировать блок расширенной памяти

;

;.Descr        Эта функция блокирует блок расширенной

;              памяти и возвращает 31 разряд его

;              физического адреса.

;

;.Proto        long XMM_LockExtended(unsigned handle);

;

;.Params       handle - индекс блокируемого блока памяти;

;

;.Return       < 0 - блок не заблокирован,

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



;              > 0L  - блок заблокирован, функция

;                      возвращает физический адрес блока

;                      памяти.

;

;**

_XMM_LockExtended proc near

          c_begin

          mov  ah, 0Ch

          mov  dx, [bp+4]

          call [XMM_Control]

          xchg ax, bx

          dec  bx

          jz   XMML_Success

          mov  dh, al

XMML_Success:

                c_end

_XMM_LockExtended endp

;**

;.Name         _XMM_UnLockExtended

;.Title        Разблокировать блок расширенной памяти

;

;.Descr        Эта функция разблокирует блок расширенной

;              памяти.

;

;.Proto        long XMM_UnLockExtended(unsigned handle);

;

;.Params       handle - индекс блока памяти;

;

;.Return       < 0 - блок не разблокирован,

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

;              0L  - блок разблокирован.

;

;**

_XMM_UnLockExtended proc near

          c_begin

          mov  ah, 0Dh

          mov  dx, [bp+4]

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @successC

          mov  dh, bl

@successC:

          c_end

_XMM_UnLockExtended endp

;**

;.Name         _XMM_GetHandleLength

;.Title        Получить длину блока расширенной памяти

;

;.Descr        Эта функция возвращает длину блока

;              расширенной памяти по его индексу.

;

;.Proto        long XMM_GetHandleLength(unsigned handle);

;

;.Params       handle - индекс блока памяти;

;

;.Return       < 0 - произошла ошибка,

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

;              > 0L  - длина блока в килобайтах.

;

;**

_XMM_GetHandleLength proc near

          c_begin

          mov  ah, 0Eh

          mov  dx, [bp+4]

          call [XMM_Control]

          or   ax, ax

          mov  ax, dx

          mov  dx, 0

          jnz  @successD

          mov  dh, bl

@successD:

          c_end

_XMM_GetHandleLength endp



;**

;.Name         _XMM_GetHandleInfo

;.Title        Получить информацию о блоке расширенной памяти

;

;.Descr        Эта функция возвращает общее

;              количество индексов в системе и

;              содержимое счетчика блокирования для

;              заданного индекса.

;

;.Proto        long XMM_GetHandleInfo(unsigned handle);

;

;.Params       handle - индекс блока памяти;

;

;.Return       < 0 - произошла ошибка,

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

;              > 0L  - младший байт - общее количество

;                      индексов в системе;

;                      старший байт - счетчик блокирования.

;

;**

_XMM_GetHandleInfo proc near

          c_begin

          mov  ah, 0Eh

          mov  dx, [bp+4]

          call [XMM_Control]

          mov  dx, bx

          or   ax, ax

          mov  ax, dx

          mov  dx, 0

          jnz  @successE

          mov  dh, bl

@successE:

          c_end

_XMM_GetHandleInfo endp

;**

;.Name         _XMM_ReallocateExtended

;.Title        Изменить размер блока расширенной памяти

;

;.Descr        Эта функция изменяет размер выделенного

;              блока расширенной памяти.

;

;.Proto        long XMM_ReallocateExtended(unsigned handle,

;                 unsigned new_size);

;

;.Params       handle - индекс блока памяти;

;              new_size - новый размер блока памяти

;                      в килобайтах;

;

;.Return       < 0 - блок не распределен,

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

;              > 0L  - младший байт содержит индекс

;                      полученного блока памяти.

;

;**

_XMM_ReallocateExtended proc near

          c_begin

          mov  ah, 0Fh

          mov  dx, [bp+4]

          mov  bx, [bp+6]

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @successF

          mov  dh, bl

@successF:

          c_end

_XMM_ReallocateExtended endp



;**

;.Name         _XMM_RequestUMB

;.Title        Запросить область UMB

;

;.Descr        Эта функция пытается зарезервировать для

;              программы область UMB

;

;.Proto        long  XMM_RequestUMB(unsigned space);

;

;.Params       space - размер требуемой области

;                      в параграфах;

;

;.Return       < 0 - область UMB не назначена программе,

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

;                    максимальный размер доступного блока

;                    в младшем слове (16 разрядов);

;              > 0L  - область UMB назначена программе,

;                    младшее слово содержит сегмент блока

;                    UMB, старший - размер выделенного

;                    блока UMB.

;

;**

_XMM_RequestUMB proc near

          c_begin

          mov  ah, 10h

          mov  dx, [bp+4]

          call [XMM_Control]

          xchg bx, ax

          dec  bx

          jz   RUMB_Success

          xchg ax, dx

          mov  dh, dl

RUMB_Success:

          c_end

_XMM_RequestUMB endp

;**

;.Name         _XMM_ReleaseUMB

;.Title        Освободить область UMB

;

;.Descr        Эта функция пытается освободить

;              область UMB

;

;.Proto        long  XMM_ReleaseUMB(unsigned segment);

;

;.Params       segment - сегмент освобождаемого блока UMB*

;

;.Return       < 0 - область UMB не освобождена,

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

;              0L - область UMB освобождена.

;

;**

_XMM_ReleaseUMB proc near

          c_begin

          mov  ah, 11h

          mov  dx, [bp+4]

          call [XMM_Control]

          xor  dx, dx

          dec  ax

          jz   @success10

          mov  dh, bl

@success10:

          c_end

_XMM_ReleaseUMB endp

  END


Программа CDINFO


Программа CDINFO вызывает как функции расширения MSCDEX.EXE, так и драйвер устройства чтения CD-ROM, получая различную информацию как о расширении, так и о компакт-диске.

Вот пример листинга, полученного при работе программы CDINFO на виртуальной машине DOS в среде Microsoft Windows 95:

CDINFO, (c) A. Frolov, 1997

MSCDEX version: 2.95

Found 1 CD Unit, start unit: G

CD-ROM letters: G

Status of CD Drive G: 00000390

VolumeSize: 29460 blocks

Tracks: (1 - 1) 8252

track 1: location: 512, info: 41 * digital, copy prohibited *

Здесь в компьютере установлено только одно устройство чтения CD?ROM, в котором находился один цифровой компакт-диск.

Ниже мы представили листинг, полученный при аналогичных условиях на компьютере, оборудованном двумя устройствами чтения CD-ROM. В первое устройство (H:) был вставлен звуковой диск, а во второе (I:) - комбинированный диск с одной цифровой и тремя звуковыми дорожками:

CDINFO, (c) A. Frolov, 1997

MSCDEX version: 2.21

Found 2 CD Unit, start unit: H

CD-ROM letters: H I

Status of CD Drive H: 00000390

VolumeSize: 302375 blocks

Tracks: (1 - 15) 2866

track 1: location: 512, info: 01 * audio, copy prohibited *

track 2: location: 79922, info: 01 * audio, copy prohibited *

track 3: location: 466492, info: 01 * audio, copy prohibited *

track 4: location: 788490, info: 01 * audio, copy prohibited *

track 5: location: 1120281, info: 01 * audio, copy prohibited *

track 6: location: 1453334, info: 01 * audio, copy prohibited *

track 7: location: 1778979, info: 01 * audio, copy prohibited *

track 8: location: 2042649, info: 01 * audio, copy prohibited *

track 9: location: 2363412, info: 01 * audio, copy prohibited *

track 10: location: 2565639, info: 01 * audio, copy prohibited *

track 11: location: 2823479, info: 01 * audio, copy prohibited *

track 12: location: 3094814, info: 01 * audio, copy prohibited *

track 13: location: 3419404, info: 01 * audio, copy prohibited *

track 14: location: 3740478, info: 01 * audio, copy prohibited *


track 15: location: 4130306, info: 01 * audio, copy prohibited *

Status of CD Drive I: 00000390

VolumeSize: 278505 blocks

Tracks: (1 - 4) 13598

track 1: location: 512, info: 41 * digital, copy prohibited *

track 2: location: 3282733, info: 01 * audio, copy prohibited *

track 3: location: 3608079, info: 01 * audio, copy prohibited *

track 4: location: 3801921, info: 01 * audio, copy prohibited *

Заметим, что в среде виртуальной машины операционной системы Microsoft Windows NT эта программа показывает неверные результаты. Скорее всего это происходит из-за неправильной работы эмулятора расширения MSCDEX.

Исходный текст программы CDINFO вы найдете в листинге 9.1.

Листинг 9.1. Файл cdinfo\cdinfo.с

// =====================================================

// Прсмотр различной информации об устройствах

// чтения CD-ROM и компакт-дисках

// с помощью интерфейса MSCDEX и обращением к драйверу

// устройства чтения CD-ROM

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <dos.h>

#include <memory.h>

 

typedef unsigned char BYTE;

typedef unsigned int  WORD;

typedef unsigned long DWORD;

// Необходимо для обеспечения выравнивания

// полей структур на границу байта

#pragma pack(1)

// Заголовок запроса для обращения к драйверу

typedef struct _ReqHdr

{

  BYTE bSize;

  BYTE bSubUnit;

  BYTE bCmd;

  WORD wStatus;

  BYTE bReserved[8];   

} ReqHdr;

 

// Запрос IOCTL Input

typedef struct _IOCTL_Input

{            

  ReqHdr rh;

  BYTE   bMediaDescriptor;

  DWORD  lpTransferAddress;

  WORD   wDataSize;

  WORD   wStartSector;

  DWORD  lpVolID;

} IOCTL_Input;      

// Запрос на получение информации о компакт-диске

typedef struct _DiskInfo

{

  BYTE bControl;



  BYTE bLowest;

  BYTE bHighest;

  DWORD dwTotal;

} DiskInfo;

// Запрос на получение информации

// о дорожке компакт-диска

typedef struct _TrackInfo

{

  BYTE  bControl;

  BYTE  bTrack;

  DWORD dwLoc;

  BYTE  bInfo;

} TrackInfo;

// Запрос для определения текущего состояния

// устройства чтения CD-ROM

typedef struct _DeviceStatus

{

  BYTE bControl;

  DWORD dwParam;

} DeviceStatus;

// Запрос для определения общего количества

// секторов на компакт-диске

typedef struct _VolumeSize

{

  BYTE bControl;

  DWORD dwVolumeSize;

} VolumeSize;

#pragma pack()

// Прототипы функций

void GetCDLetters(BYTE *bLetters);

void CallCDDriver(void *rh, int nCDUnit);

int GetDiskInfo(int nCDUnit);

int GetDeviceStatus(int nCDUnit);

int GetVolumeSize(int nCDUnit);

int GetTrackInfo(int iTrack, int nCDUnit);

void delay(int ms);

// Регистры для вызова функции int86

union REGS rg;                     

// Количество установленных устройств чтения CD-ROM

int nCDUnits;

// Номер первого устройства чтения CD-ROM

int nCDStartUnit;

// Слово состояния после вызова драйвера CD-ROM

int iStatus;

// Информация о компакт-диске

DiskInfo     di;            

// Состояние устройства чтения CD-ROM

DeviceStatus ds;                    

// Объем компакт-диска

VolumeSize   vs;     

// Информация о дорожке

TrackInfo    ti;

BYTE bLetters[26];

// ---------------------------------------------------

// main

// Точка входа в программу

// ---------------------------------------------------

int main()

{

  int iRetry;

  int i, j;

 

  printf("CDINFO, (c) A. Frolov, 1997\n\n");

 

  // Проверяем, установлена ли программа MSCDEX

  rg.x.ax = 0x1500;

  rg.x.bx = 0;

  int86(0x2f, &rg, &rg);

  if(rg.x.bx == 0)

  {

    printf("MSCDEX is not installed\n");

    return -1;

  }

 

  else       

  {

    // Сохраняем общее количество устройств чтения CD-ROM



    nCDUnits = rg.x.bx;

   

    // Сохраняем номер первого такого устройства

    nCDStartUnit = rg.x.cx;

    // Определяем и отображаем вресию MSCDEX

    rg.x.ax = 0x150c;

    int86(0x2f, &rg, &rg);

    printf("MSCDEX version: %d.%d\n", rg.h.bh, rg.h.bl);

    // Отображаем количество найденных устройств чтения

    // CD-ROM и номер первого устройства

    printf("Found % d CD Unit, start unit: %c\n",

      nCDUnits, nCDStartUnit + 'A');

  }

  // Получаем массив номеров устройств чтения CD-ROM

  GetCDLetters(bLetters);

 

  // Отображаем обозначения всех устройств CD-ROM

  printf("CD-ROM letters: ");

  for(i = 0; i < nCDUnits; i++)

  {

    printf("%c ", bLetters[i] + 'A'); 

  }

  printf("\n");

  // Цикл по всем устройствам чтения CD-ROM

  for(i = 0; i < nCDUnits; i++)

  {

    // Определяем и отображаем состояние устройства

    iStatus = GetDeviceStatus(bLetters[i]);

    if(iStatus != 0x0100)

    {

      printf("GetDeviceStatus status: %04.4X\n", iStatus);

      printf("GetDeviceStatus failed\n");

      exit(1);              

    }

    printf("\nStatus of CD Drive %c: %08.8X\n",

      bLetters[i] + 'A', ds.dwParam);

    // Определяем и отображаем объем устройства

    iStatus = GetVolumeSize(bLetters[i]);

    if(iStatus != 0x0100)

    {

      printf("GetVolumeSize status: %04.4X\n", iStatus);

      printf("GetVolumeSize failed\n");

    }

    else

      printf("VolumeSize: %ld blocks\n", vs.dwVolumeSize);

    // Определяем и отображаем информацию о

    // компакт-диске

    iStatus = GetDiskInfo(bLetters[i]);

   

    // Делаем три попытки получения информации

    iRetry = 0;

    while(iStatus != 0x0100)

    {

      printf("GetDiskInfo status: %04.4X\n", iStatus);

   

      // Задержка длительностью 1 с

      delay(1000);

   

      iRetry++;



   

      if(iRetry == 3)

      {

        printf("GetDiskInfo failed\n");

        break;

      }

      iStatus = GetDiskInfo(bLetters[i]);

    }

 

    // Если удалось получить информацию о компакт-диске,

    // исследуем его дорожки

    if(iRetry != 3)

    {

      // Выводим номера дорожек

      printf("Tracks: (%d - %d) %d\n",

        di.bLowest, di.bHighest, di.dwTotal);

      // Цикл по всем дорожкам диска

      for(j = di.bLowest; j <= di.bHighest; j++)

      {

        // Получаем информацию о дорожке и отображаем ее

        GetTrackInfo(j, bLetters[i]);

        printf("track %d: location: %ld, info: %02.2X",

          j, ti.dwLoc, ti.bInfo);

     

        // Определяем тип дорожки - звуковая дорожка

        // или дорожка с данными

        if(ti.bInfo & 0x40)

          printf(" * digital");

        else

          printf(" * audio");

        // Определяем, разрашено ли копирование дорожки

        if(ti.bInfo & 0x20)

          printf(", copy permitted *\n");

        else

          printf(", copy prohibited *\n");

      }

    } 

  }

  return 0;

}

// ---------------------------------------------------

// GetDiskInfo

// Получение информации о компакт-диске

// ---------------------------------------------------

int GetDiskInfo(int nCDUnit)

{

  // Заголовок команды IOCTL Input

  IOCTL_Input cmd;

 

  // Очищаем заголовок

  memset(&cmd, 0, sizeof(IOCTL_Input ));

 

  // Заполняем заголовок

  cmd.rh.bSize    = 26;

  cmd.rh.bSubUnit = 0;

  cmd.rh.bCmd     = 3;

 

  cmd.bMediaDescriptor  = 0;

  cmd.lpTransferAddress = (DWORD)(void far *)&di;

  cmd.wDataSize         = 7;

  cmd.wStartSector      = 0;

  cmd.lpVolID           = (DWORD)(void far *)NULL;

 

  di.bControl = 10;

 

  // Вызываем драйвер

  CallCDDriver(&cmd, nCDUnit);

 

  return cmd.rh.wStatus;



// ---------------------------------------------------



// GetTrackInfo

// Получение информации о дорожке компакт-диска

// ---------------------------------------------------

int GetTrackInfo(int iTrack, int nCDUnit)

{

  IOCTL_Input cmd;

 

  memset(&cmd, 0, sizeof(IOCTL_Input ));

 

  cmd.rh.bSize    = 26;

  cmd.rh.bSubUnit = 0;

  cmd.rh.bCmd     = 3;

 

  cmd.bMediaDescriptor  = 0;

  cmd.lpTransferAddress = (DWORD)(void far *)&ti;

  cmd.wDataSize         = 7;

  cmd.wStartSector      = 0;

  cmd.lpVolID           = (DWORD)(void far *)NULL;

 

  ti.bControl = 11;

  ti.bTrack = iTrack;

 

  CallCDDriver(&cmd, nCDUnit);

 

  return cmd.rh.wStatus;



// ---------------------------------------------------

// GetDeviceStatus

// Определение состояния устройства чтения CD-ROM

// ---------------------------------------------------

int GetDeviceStatus(int nCDUnit)

{

  IOCTL_Input cmd;

 

  memset(&cmd, 0, sizeof(IOCTL_Input ));

 

  cmd.rh.bSize    = 26;

  cmd.rh.bSubUnit = 0;

  cmd.rh.bCmd     = 3;

 

  cmd.bMediaDescriptor  = 0;

  cmd.lpTransferAddress = (DWORD)(void far *)&ds;

  cmd.wDataSize         = 5;

  cmd.wStartSector      = 0;

  cmd.lpVolID           = (DWORD)(void far *)NULL;

 

  ds.bControl = 6;

 

  CallCDDriver(&cmd, nCDUnit);

  return cmd.rh.wStatus;



// ---------------------------------------------------

// GetVolumeSize

// Определение объема компакт-диска

// ---------------------------------------------------

int GetVolumeSize(int nCDUnit)

{

  IOCTL_Input cmd;

 

  memset(&cmd, 0, sizeof(IOCTL_Input ));

 

  cmd.rh.bSize    = 26;

  cmd.rh.bSubUnit = 0;

  cmd.rh.bCmd     = 3;

 

  cmd.bMediaDescriptor  = 0;

  cmd.lpTransferAddress = (DWORD)(void far *)&vs;

  cmd.wDataSize         = 5;

  cmd.wStartSector      = 0;

  cmd.lpVolID           = (DWORD)(void far *)NULL;

 

  vs.bControl = 8;

 

  CallCDDriver(&cmd, nCDUnit);

  return cmd.rh.wStatus;



// ---------------------------------------------------



// CallCDDriver

// Вызов драйвера компакт-диска

// ---------------------------------------------------

void CallCDDriver(void *rh, int nCDUnit)

{

  static union REGS rg;

  static struct SREGS srg;

 

  segread(&srg);

  rg.x.ax = 0x1510;

  rg.x.cx = nCDUnit;

  rg.x.bx = FP_OFF(rh); 

 

  int86x(0x2f, &rg, &rg, &srg);

}

// ---------------------------------------------------

// GetCDLetters

// Заполнение массива номерами установленных

// в системе устройств чтения компакт-диска

// ---------------------------------------------------

void GetCDLetters(BYTE *bLetters)

{

  static union REGS rg;

  static struct SREGS srg;

 

  segread(&srg);

  rg.x.ax = 0x150d;

  rg.x.bx = FP_OFF(bLetters); 

 

  int86x(0x2f, &rg, &rg, &srg);

}

// ---------------------------------------------------

// delay

// Формирование задержки по таймеру

// ---------------------------------------------------

void delay(int ms)

{

  int ticks;   

 

  ticks = ms / 55;

  _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

  }

}


Программа CDPLAY


Программа CDPLAY предназначена для проигрывания дорожек звуковых компакт-дисков. При запуске этой программы необходимо указать параметр – номер блока, с которого должно выполняться проигрывание.

Ниже мы привели пример запуска программы, передав ей адрес 512:

CDPLAY, (c) A. Frolov, 1997

Track Red book: 512

Track Sierra: 0

MSCDEX version: 2.95

Found 1 CD Unit, start unit: G

CD-ROM letters: G

Started. Press any key to stop and eject CD

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

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

Исходный текст программы CDPLAY приведен в листинге 9.2.

Листинг 9.2. Файл cdplay\cdplay.с

// =====================================================

// Проигрывание звуковых компакт-дисков

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <dos.h>

#include <memory.h>

 

typedef unsigned char BYTE;

typedef unsigned int  WORD;

typedef unsigned long DWORD;

// Необходимо для обеспечения выравнивания

// полей структур на границу байта

#pragma pack(1)

// Заголовок запроса для обращения к драйверу

typedef struct _ReqHdr

{

  BYTE bSize;

  BYTE bSubUnit;

  BYTE bCmd;

  WORD wStatus;

  BYTE bReserved[8];   

} ReqHdr;

 

typedef struct _PlayAudio

{

  ReqHdr rh;

  BYTE bMode;

  DWORD dwLoc;

  DWORD dwSectorNum;

} PlayAudio;

// Запрос IOCTL Output

typedef struct _IOCTL_Output

{            

  ReqHdr rh;

  BYTE   bMediaDescriptor;


  DWORD  lpTransferAddress;

  WORD   wDataSize;

  WORD   wStartSector;

  DWORD  lpVolID;

} IOCTL_Output;      

// Запрос на извлечение компакт-диска

typedef struct _EjectDisk

{

  BYTE  bControl;

} EjectDisk;

#pragma pack()

// Прототипы функций

void GetCDLetters(BYTE *bLetters);

void CallCDDriver(void *rh, int nCDUnit);

int PlayAudioTrack( DWORD dwLoc, DWORD dwSectorNum, int nCDUnit);

int StopAudio(int nCDUnit);

int DeviceOpen(int nCDUnit);

int DeviceClose(int nCDUnit);

int EjectCD(int nCDUnit);

DWORD Red2Sierra(DWORD dwRedLoc);

// Регистры для вызова функции int86

union REGS rg;                     

// Количество установленных устройств чтения CD-ROM

int nCDUnits;

// Номер первого устройства чтения CD-ROM

int nCDStartUnit;

// Слово состояния после вызова драйвера CD-ROM

int iStatus;

                               

// Массив номеров установленных устройств CD-ROM

BYTE bLetters[26];

// ---------------------------------------------------

// main

// Точка входа в программу

// ---------------------------------------------------

int main(int argc, char *argv[])

{

  int i;

 

  DWORD dwStartTrack;

 

  printf("CDPLAY, (c) A. Frolov, 1997\n\n");

 

  dwStartTrack = 1;

  if(argc == 2)

  {

    dwStartTrack = atol(argv[1]);

    printf("Track Red book: %ld\n", dwStartTrack);

   

  }

  else

  {

    printf("Usage: CDPLAY <Red book sector address>\n");

    return -1;

  }

 

  // Преобразование адреса сектора в формат Sierra

  dwStartTrack = Red2Sierra(dwStartTrack);

  printf("Track Sierra: %ld\n", dwStartTrack);

 

  // Проверяем, установлена ли программа MSCDEX

  rg.x.ax = 0x1500;

  rg.x.bx = 0;

  int86(0x2f, &rg, &rg);

  if(rg.x.bx == 0)

  {

    printf("MSCDEX is not installed\n");

    return -1;

  }

 

  else       

  {

    // Сохраняем общее количество устройств чтения CD-ROM

    nCDUnits = rg.x.bx;



   

    // Сохраняем номер первого такого устройства

    nCDStartUnit = rg.x.cx;

    // Определяем и отображаем вресию MSCDEX

    rg.x.ax = 0x150c;

    int86(0x2f, &rg, &rg);

    printf("MSCDEX version: %d.%d\n", rg.h.bh, rg.h.bl);

    // Отображаем количество найденных устройств чтения

    // CD-ROM и номер первого устройства

    printf("Found % d CD Unit, start unit: %c\n",

      nCDUnits, nCDStartUnit + 'A');

  }

  // Получаем массив номеров устройств чтения CD-ROM

  GetCDLetters(bLetters);

 

  // Отображаем обозначения всех устройств CD-ROM

  printf("CD-ROM letters: ");

  for(i = 0; i < nCDUnits; i++)

  {

    printf("%c ", bLetters[i] + 'A'); 

  }

  printf("\n");

  // Открываем устройство

  iStatus = DeviceOpen(bLetters[0]);

  if(iStatus & 0x8000)

  {

    printf("DeviceOpen status: %04.4X\n", iStatus);

    return -1;

  }

  // Запускаем проигрывание

  iStatus =

    PlayAudioTrack(dwStartTrack, 0xffffffff, bLetters[0]);

  if(iStatus & 0x8000)

  {

    printf("PlayAudioTrack status: %04.4X\n", iStatus);

    return -1;

  }

  printf("Started. Press any key to stop and eject CD\n");

 

  // Ожидаем, пока пользователь не нажмет клавишу

  getch();

  // Останавливаем проигрывание

  iStatus = StopAudio(bLetters[0]);

  if(iStatus & 0x8000)

  {

    printf("StopAudio status: %04.4X\n", iStatus);

    return -1;

  }

  // Извлекаем диск

  iStatus = EjectCD(bLetters[0]);

  if(iStatus & 0x8000)

  {

    printf("EjectCD status: %04.4X\n", iStatus);

    return -1;

  }

  // Закрываем устройство

  iStatus = DeviceClose(bLetters[0]);

  if(iStatus & 0x8000)

  {

    printf("DeviceClose status: %04.4X\n", iStatus);

    return -1;

  }

  return 0;

}

// ---------------------------------------------------

// PlayAudioTrack



// Запуск проигрывания звукового компакт-диска

// ---------------------------------------------------

int PlayAudioTrack( DWORD dwLoc, DWORD dwSectorNum, int nCDUnit)

{

  PlayAudio cmd;

 

  memset(&cmd, 0, sizeof(PlayAudio));

 

  cmd.rh.bSize    = 22;

  cmd.rh.bSubUnit = 0;

  cmd.rh.bCmd     = 132;

 

  cmd.bMode  = 0;

  cmd.dwLoc  = dwLoc;

  cmd.dwSectorNum  = dwSectorNum;

 

  CallCDDriver(&cmd, nCDUnit);

  return cmd.rh.wStatus;



// ---------------------------------------------------

// StopAudio

// Остановка проигрывания звукового компакт-диска

// ---------------------------------------------------

int StopAudio(int nCDUnit)

{

  ReqHdr cmd;

 

  memset(&cmd, 0, sizeof(ReqHdr));

 

  cmd.bSize    = 13;

  cmd.bSubUnit = 0;

  cmd.bCmd     = 133;

 

  CallCDDriver(&cmd, nCDUnit);

  return (cmd.wStatus);



// ---------------------------------------------------

// DeviceOpen

// Открывание устройства

// ---------------------------------------------------

int DeviceOpen(int nCDUnit)

{

  ReqHdr cmd;

 

  memset(&cmd, 0, sizeof(ReqHdr));

 

  cmd.bSize    = 13;

  cmd.bSubUnit = 0;

  cmd.bCmd     = 13;

 

  CallCDDriver(&cmd, nCDUnit);

  return (cmd.wStatus);



// ---------------------------------------------------

// DeviceClose

// Закрывание устройства

// ---------------------------------------------------

int DeviceClose(int nCDUnit)

{

  ReqHdr cmd;

 

  memset(&cmd, 0, sizeof(ReqHdr));

 

  cmd.bSize    = 13;

  cmd.bSubUnit = 0;

  cmd.bCmd     = 14;

 

  CallCDDriver(&cmd, nCDUnit);

  return (cmd.wStatus);



// ---------------------------------------------------

// EjectCD

// Извлечение компакт-диска

// ---------------------------------------------------

int EjectCD(int nCDUnit)

{

  IOCTL_Output cmd;

  EjectDisk ed;

 

  memset(&cmd, 0, sizeof(IOCTL_Output));

 

  cmd.rh.bSize    = 14;

  cmd.rh.bSubUnit = 0;

  cmd.rh.bCmd     = 12;



 

  cmd.bMediaDescriptor  = 0;

  cmd.lpTransferAddress = (DWORD)(void far *)&ed;

  cmd.wDataSize         = 1;

  cmd.wStartSector      = 0;

  cmd.lpVolID           = (DWORD)(void far *)NULL;

 

  ed.bControl = 0;

 

  CallCDDriver(&cmd, nCDUnit);

  return cmd.rh.wStatus;



// ---------------------------------------------------

// CallCDDriver

// Вызов драйвера компакт-диска

// ---------------------------------------------------

void CallCDDriver(void *rh, int nCDUnit)

{

  static union REGS rg;

  static struct SREGS srg;

 

  segread(&srg);

  rg.x.ax = 0x1510;

  rg.x.cx = nCDUnit;

  rg.x.bx = FP_OFF(rh); 

 

  int86x(0x2f, &rg, &rg, &srg);

}

// ---------------------------------------------------

// GetCDLetters

// Заполнение массива номерами установленных

// в системе устройств чтения компакт-диска

// ---------------------------------------------------

void GetCDLetters(BYTE *bLetters)

{

  static union REGS rg;

  static struct SREGS srg;

 

  segread(&srg);

  rg.x.ax = 0x150d;

  rg.x.bx = FP_OFF(bLetters); 

 

  int86x(0x2f, &rg, &rg, &srg);

}

// ---------------------------------------------------

// Преобразование адреса дорожки из формата Red book

// в формат Sierra

// ---------------------------------------------------

DWORD Red2Sierra(DWORD dwRedLoc)

{

  BYTE bMin, bSec, bFrame;

  bMin   = (BYTE)((dwRedLoc >> 16) & 0xff);

  bSec   = (BYTE)((dwRedLoc >> 8) & 0xff);

  bFrame = (BYTE)(dwRedLoc & 0xff);

 

  return (DWORD)bMin * 75 * 60 + (DWORD)bSec * 75 +

    (DWORD)bFrame - 150;

}


Программа CHKBUF


Приведем исходный текст программы CHKBUF, выводящей на экран в цикле символ '*' (листинг 2.3). Если нажать любую клавишу, кроме <Esc>, программа выводит на экран строку текста - инструкцию для завершения работы программы. Если же нажать на клавишу <Esc>, работа программы будет завершена.

Листинг 2.3. Файл chkbuf\chkbuf.c

// =====================================================

// Демонстрация способа проверки буфера клавиатуры

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <dos.h>

int main(void)

{

  union REGS  rg;

  int   i, zflag;

 

  printf("CHKBUF, (c) A. Frolov, 1997\n");

 

  for(;;)

  {

    // Выводим в цикле символ '*'

    putchar('*');

    // Небольшая задержка во времени

    for(i=0; i<30000; i++);

    // Вызываем прерывание INT 16h для проверки буфера

    // клавиатуры. Устанавливаем флаг, который будет сброшен

    // при нажатии на любую клавишу

    zflag = 1;

    _asm

    {

      mov   ax, 0100h

      int   16h

      // Если клавишу не нажимали,

      // продолжаем выполнение программы

      jz    nokey      

      // В противном случае сбрасываем флаг

      mov   zflag, 0

     

      nokey:

    }

    if(zflag == 0)

    {

      // Если флаг сброшен, читаем код нажатой клавиши из

      // буфера при помощи функции 01h прерывания INT 16h

      rg.h.ah = 0;

      int86(0x16, &rg, &rg);

      // Если была нажата клавиша <Esc>,

      // завершаем работу программы

      if(rg.h.ah == 1)

      {

        // Выводим на экран содержимое регистров AH и AL,

        // содержащих, соответственно, скан-код и код ASCII

        // нажатой клавиши

        printf("\nScan = %02.2X Ascii = %02.2X",

          rg.h.ah, rg.h.al);

       

        break;

      }

      else

        printf("\nPress <ESC> to exit\n");

    }

  }

  return 0;

}



Программа CMOSSHOW


Программа CMOSSHOW читает в массив первые 64 ячейки памяти CMOS и отображает содержимое некоторых из них:

CMOS Show (C)A. Frolov, 1997

RTC:              22 00 30 00 17 00 03 19 08 97 a6 02 00

Diagnostics byte: 08

Shutdown byte:    00

Reserved:         00 00 00 00 00 00 00 00 00 00 00 00

Extended RAM:     16384 Kbyte

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

Исходный текст программы CMOSSHOW вы найдете в листинге 1.3.

Листинг 1.3. Файл cmosshow\cmosshow.c

// =====================================================

// Чтение и отображение ячеек памяти CMOS

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

int main()

{

  unsigned char cmos[64];

  int i;

  unsigned long nExtRam;

  printf("\nCMOS Show (C)A. Frolov, 1997\n\n");

  // Читаем 64 ячейки CMOS-памяти в массив cmos

  for(i=0; i<64; i++)

  {

    outp(0x70,i);

    cmos[i]=inp(0x71);

  }

  // Отображаем ячейки часов реального времени

  printf("\nRTC:              ");

  for(i=0; i<0xd; i++)

  {

    printf("%02.2x ",(unsigned)cmos[i]);

  }

  // Отображаем состояние байта диагностики

  // после включения питания

  printf("\nDiagnostics byte: %02.2x",cmos[0xe]);

  // Отображаем содержимое байта отключения

  printf("\nShutdown byte:    %02.2x\n",cmos[0xf]);

  // Отображаем содержимое зарезервированных ячеек

  printf("Reserved:         ");

  for(i=0x34; i<0x40; i++)

  {

    printf("%02.2x ",(unsigned)cmos[i]);

  }

 

  // Вычисляем объем расширенной памяти и отображаем

  // его на консоли

  nExtRam = ((unsigned long)cmos[0x18] << 8) + cmos[0x17];

  printf("\nExtended RAM:     %ld Kbyte\n", nExtRam);

   

  getch();

  return 0;

}



Программа COMTEST


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

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

Листинг 6.1. Файл comtest\comtest.с

// =====================================================

// Работа с асинхронным адаптером COM1.

// Перед запуском программы необходимо замкнуть

// контакты 2 и 3 разъема COM1

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

typedef struct _AUX_MODE_

{

  union

  {

    struct

    {

      unsigned char len : 2, // длина символа

           stop         : 1, // число стоп-битов

           parity       : 2, // контроль четности

           stuck_parity : 1, // фиксация четности

           en_break_ctl : 1, // установка перерыва

           dlab         : 1; // загрузка регистра делителя

    } ctl_word;

    char ctl;

  } ctl_aux;

  unsigned long baud; // скорость передачи данных

} AUX_MODE;

void aux_stat(AUX_MODE *mode, int port);

int aux_init(AUX_MODE *mode, int port, int imask);

void aux_outp(char chr, int port);

char aux_inp(int port);

int main(void)

{

  AUX_MODE amd;

  aux_stat(&amd, 0);

  printf("\nСостояние порта COM1:"

    "\nКод длины символа:    %d"

    "\nКод числа стоп-битов: %d"

    "\nКонтроль четности:    %d"

    "\nСкорость передачи:    %lu",

    amd.ctl_aux.ctl_word.len,

    amd.ctl_aux.ctl_word.stop,

    amd.ctl_aux.ctl_word.parity,

    (unsigned long)amd.baud);

  amd.baud = 115200;

  aux_init(&amd, 0, 0);


  aux_stat(&amd, 0);

  printf("\nСостояние порта COM1:"

    "\nКод длины символа:    %d"

    "\nКод числа стоп-битов: %d"

    "\nКонтроль четности:    %d"

    "\nСкорость передачи:    %lu",

    amd.ctl_aux.ctl_word.len,

    amd.ctl_aux.ctl_word.stop,

    amd.ctl_aux.ctl_word.parity,

    (unsigned long)amd.baud);

  printf("\n\nТестирование асинхронного адаптера."

    "\nНажимайте клавиши!"

    "\nДля завершения работы нажмите <Contril+C>\n");

  for(;;)

  {

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

    // в асинхронный адаптер

    aux_outp((char)getch(), 0);

    // Вводим символ из асинхронного адаптера и

    // отображаем его на экране

    putchar(aux_inp(0));

  }

  return 0;

}

/**

*.Name         aux_stat

*.Title        Определение режима асинхронного адаптера

*

*.Descr        Эта функция считывает текущий режим

*              асинхронного порта и записывает его

*              в структуру с типом AUX_MODE

*

*.Proto        void aux_stat(AUX_MODE *mode, int port);

*

*.Params       AUX_MODE mode - структура, описывающая

*              протокол и режим работы порта:

*

*              int port - номер асинхронного адаптера:

*                 0 - COM1, 1 - COM2

**/

void aux_stat(AUX_MODE *mode, int port)

{

  unsigned long b;

  // Запоминаем режим адаптера

  mode->ctl_aux.ctl = (char)inp(0x3fb - 0x100 * port);

  // Устанавливаем старший бит режима

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

  outp(0x3fb - 0x100 * port, mode->ctl_aux.ctl | 0x80);

  // Считываем значение регистра делителя

  b = inp(0x3f9 - 0x100 * port); b = b << 8;

  b += inp(0x3f8 - 0x100 * port);

  // Преобразуем его в боды

  switch (b)

  {

    case 1040: b = 110; break;

    case 768: b = 150; break;

    case 384: b = 300; break;

    case 192: b = 600; break;

    case 96: b = 1200; break;



    case 48: b = 2400; break;

    case 24: b = 4800; break;

    case 12: b = 9600; break;

    case 6: b = 19200; break;

    case 3: b = 38400; break;

    case 2: b = 57600; break;

    case 1: b = 115200; break;

    default: b=0; break;

  }

  mode->baud = b;

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

  outp(0x3fb - 0x100 * port, mode->ctl_aux.ctl & 0x7f);

}

/**

*.Name         aux_init

*.Title        Инициализация асинхронного адаптера

*

*.Descr        Эта функция инициализирует асинхронные

*              адаптеры, задавая протокол обмена данными

*              и скорость обмена данными

*

*.Proto        int aux_init(AUX_MODE *mode, int port,

*                                        int imask);

*

*.Params       AUX_MODE *mode - указатель на структуру,

*                        описывающую протокол и режим работы

*                        порта;

*

*              int port - номер асинхронного адаптера:

*                 0 - COM1, 1 - COM2

*

*              int imask - значение для регистра маски

*                          прерываний

*

*.Return       0 - инициализация выполнена успешно;

*              1 - ошибки в параметрах инициализации.

**/

int aux_init(AUX_MODE *mode, int port, int imask)

{

  unsigned div;

  char ctl;

  // Вычисляем значение для делителя

  switch (mode->baud)

  {

    case 110: div = 1040; break;

    case 150: div = 768; break;

    case 300: div = 384; break;

    case 600: div = 192; break;

    case 1200: div = 96; break;

    case 2400: div = 48; break;

    case 4800: div = 24; break;

    case 9600: div = 12; break;

    case 19200: div = 6; break;

    case 38400: div = 3; break;

    case 57600: div = 2; break;

    case 115200: div =1; break;

    default:

      return(-1); break;

  }

  // Записываем значение делителя частоты

  ctl = inp(0x3fb - 0x100 * port);

  outp(0x3fb - 0x100 * port, ctl | 0x80);

  outp(0x3f9 - 0x100 * port, (div >> 8) & 0x00ff);



  outp(0x3f8 - 0x100 * port, div & 0x00ff);

  // Записываем новое управляющее слово

  outp(0x3fb - 0x100 * port, mode->ctl_aux.ctl & 0x7f);

  // Устанавливаем регистр управления прерыванием

  outp(0x3f9 - 0x100 * port, imask);

 

  return 0;

}

/**

*.Name         aux_outp

*.Title        Вывод символа в асинхронный адаптер

*

*.Descr        Эта функция дожидается готовности

*              передатчика и посылает символ

*

*.Proto        void aux_outp(char chr, int port);

*

*.Params       char chr - посылаемый символ;

*

*              int port - номер асинхронного адаптера:

*                 0 - COM1, 1 - COM2

**/

void aux_outp( char chr, int port)

{

  unsigned status_reg, out_reg;

  status_reg = 0x3fd - 0x100 * port;

  out_reg = status_reg - 5;

  while( (inp(status_reg) & 0x20) == 0 );

  outp(out_reg, chr);

}

/**

*.Name         aux_inp

*.Title        Ввод символа из асинхронного адаптера

*

*.Descr        Эта функция дожидается готовности

*              приемника и вводит символ из асинхронного

*              адаптера

*

*.Proto        char aux_inp(int port);

*

*.Params       int port - номер асинхронного адаптера:

*                 0 - COM1, 1 - COM2

*

*.Return       Принятый символ

**/

char aux_inp(int port)

{

  unsigned status_reg, inp_reg;

  status_reg = 0x3fd - 0x100 * port;

  inp_reg = status_reg - 5;

  while( (inp(status_reg) & 1) == 0 );

  return(inp(inp_reg));

}


Программа CPUINFO


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

*CPU Information*, (C) A. Frolov, 1997

CPU model: 5

Vendor ID: GenuineIntel

CPU Signature    00000619

CPU Feature EDX  0000F9FF

CPU type:     0

CPU family:   6

CPU model:    1

CPU stepping: 9

FPU detected

В листинге 1.4 вы найдете исходный текст модуля, составленного на языке ассемблера. В этом модуле определены все функции, необходимые для распознавания процессора и получения его характеристик.

Листинг 1.4. Файл cpuinfo\askcpu.asm

; =====================================================

; Get CPU information

;

; (C) A. Frolov, 1997

;

; E-mail: frolov@glas.apc.org

; WWW:    http://www.glasnet.ru/~frolov

;            or

;         http://www.dials.ccas.ru/frolov

; =====================================================

  .model small

CPU_ID MACRO

  db 0fh

  db 0a2h

ENDM

  .stack 100h

  .data

 

  public _vendor_id_msg

  public _cpu_model

  public _cpu_signature

  public _features_ecx

  public _features_edx

  public _features_ebx

  public _get_cpu_model

_vendor_id_msg db "............", 0dh, 0ah, "$"

_cpu_model     db 0

_cpu_signature dd 0

_features_ecx  dd 0

_features_edx  dd 0

_features_ebx  dd 0

  .code

; ============================================

; _get_cpu_model

; ============================================

  .8086

_get_cpu_model proc

  call cpu_8086

  cmp ax, 0

  jz try_80286

  mov _cpu_model, 0

  jmp end_of_detect

try_80286:

  call cpu_80286

  cmp ax, 0

  jz try_80386

  mov _cpu_model, 2

  jmp end_of_detect

try_80386:

  call cpu_80386

  cmp ax, 0

  jz try_80486

  mov _cpu_model, 3

  jmp end_of_detect

try_80486:

  call cpu_80486


  cmp ax, 0

  jz Pent_CPU

  mov _cpu_model, 4

  jmp end_of_detect

Pent_CPU:

  mov _cpu_model, 5

  .386

  pusha

  mov eax, 00h

  CPU_ID

  mov dword ptr _vendor_id_msg, ebx

  mov dword ptr _vendor_id_msg[+4], edx

  mov dword ptr _vendor_id_msg[+8], ecx

  cmp eax, 1

  jl end_of_detect

 

  mov eax, 1

  CPU_ID

  mov _cpu_signature, eax

  mov _features_ebx, ebx

  mov _features_edx, edx

  mov _features_ecx, ecx

  popa

end_of_detect:

 

  .8086

  ret

_get_cpu_model endp

; ============================================

; cpu_8086

; ============================================

cpu_8086 proc

  pushf

  pop ax

  mov cx, ax

  and ax, 0fffh

  push ax

  popf

  pushf

  pop ax

  and ax, 0f000h

  cmp ax, 0f000h

  je is_8086

  mov ax, 0

  ret

is_8086:

  mov ax, 1

  ret

cpu_8086 endp

; ============================================

; cpu_80286

; ============================================

  .286

cpu_80286 proc

  mov ax, 0f000h

  push ax

  popf

  pushf

  pop ax

  and ax, 0f000h

  jz is_80286

  mov ax, 0

  ret

is_80286:

  mov ax, 1

  ret

cpu_80286 endp

; ============================================

; cpu_80386

; ============================================

  .386

cpu_80386 proc

  pushfd

  pop eax

  mov ecx, eax

  xor eax, 40000h

  push eax

  popfd

  pushfd

  pop eax

  xor eax, ecx

  jz is_80386

  mov ax, 0

  ret

is_80386:

  push ecx

  popfd

  mov ax, 1

  ret

cpu_80386 endp

; ============================================

; cpu_80486

; ============================================

cpu_80486 proc

  pushfd

  pop eax

  mov ecx, eax

  xor eax, 200000h

  push eax

  popfd

  pushfd

  pop eax

  xor eax, ecx

  je is_80486

  mov ax, 0

  ret

is_80486:

  mov ax, 1



  ret

cpu_80486 endp

  end

Данный файл был оттранслирован при помощи пакетного файла, представленного в листинге 1.5.

Листинг 1.5. Файл cpuinfo\mk.bat

masm /Zi askcpu.asm,,,,

Главный файл программы приведен в листинге 1.6.

Листинг 1.6. Файл cpuinfo\cpuinfo.c

// =====================================================

// Определение типа процессора

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#include <memory.h>

              

extern void GET_CPU_MODEL(void);

extern char VENDOR_ID_MSG[12];

extern char CPU_MODEL;

extern long CPU_SIGNATURE;

extern long FEATURES_ECX;

extern long FEATURES_EDX;

extern long FEATURES_EBX;

int main(void)

{        

  char buf[128];

  printf("*CPU Information*, (C) A. Frolov, 1997\n\n");

  GET_CPU_MODEL();

 

  printf("CPU model: %d\n", (unsigned)CPU_MODEL);

 

  if(CPU_MODEL >= 5)

  {

    memcpy(buf, VENDOR_ID_MSG, 12);

    buf[12] = 0;

    printf("Vendor ID: %s\n\n", buf);

 

    printf("CPU Signature    %08.8X\n", CPU_SIGNATURE);

    printf("CPU Feature EDX  %08.8X\n\n", FEATURES_EDX);

   

    printf("CPU type:     %d\n",

      (CPU_SIGNATURE & 0x3000) >> 12);

    printf("CPU family:   %d\n",

      (CPU_SIGNATURE & 0xF00) >> 8);

    printf("CPU model:    %d\n",

      (CPU_SIGNATURE & 0xF0) >> 4);

    printf("CPU stepping: %d\n\n", CPU_SIGNATURE & 0xF);

   

    if(FEATURES_EDX & 0x1)

      printf("FPU detected\n");

    if(FEATURES_EDX & 0x800000)

      printf("MMX supported\n");

  }

  getch();

  return 0;

}


Программа HDWCFG


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

HDWCFG (C)A. Frolov, 1997

Configuration word: C823

HDD present

NPU present

RAM banks: 0

Video Mode: 2

Nubber of FDD: 1

Nubber of COM ports: 2

Number of LPT ports: 3

RAM istalled: 640 Kbytes

Extended RAM istalled: 0

Исходный текст программы HDWCFG представлен в листинге 1.1.

Листинг 1.1. Файл hdwcfg\hdwcfg.c

// =====================================================

// Получение информации о конфигурации компьютера

// при помощи BIOS

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <memory.h>

#include <dos.h>

// Битовые поля слова конфигурации

typedef struct _HDWCFG

{

  unsigned  HddPresent:   1; // 0

  unsigned  NpuPresent:   1; // 1

  unsigned  AmountOfRAM:  2; // 2-3

  unsigned  VideoMode:    2; // 4-5

  unsigned  NumberOfFdd:  2; // 6-7

  unsigned  DmaPresent:   1; // 8

  unsigned  NumberOfCom:  3; // 9-11

  unsigned  GamePresent:  1; // 12

  unsigned  JrComPresent: 1; // 13

  unsigned  NumberOfLpt:  2; // 14-15

} HDWCFG;    

int main(void)

{

  union REGS  rg;

  HDWCFG HdwCfg;

  unsigned uword;

  printf("\nHDWCFG (C)A. Frolov, 1997");

  // Вызываем прерывание INT 11h для получения

  // слова конфигурации компьютера

  rg.h.ah = 0x0;

  int86(0x11, &rg, &rg);

 

  // Получаем слово конфигурации и сохраняем

  // его в структуре HdwCfg

  uword = (unsigned int)rg.x.ax;

  memcpy(&HdwCfg, &uword, 2);

 

  // Выводим на экран конфигурацию компьютера

  printf("\n\nConfiguration word: %04.4X", HdwCfg);

 

  if(HdwCfg.HddPresent)


    printf("\nHDD present");

  if(HdwCfg.NpuPresent)

    printf("\nNPU present");

  printf("\nRAM banks: %d", HdwCfg.AmountOfRAM);

  printf("\nVideo Mode: %d", HdwCfg.VideoMode);

  printf("\nNubber of FDD: %d", HdwCfg.NumberOfFdd + 1);

   

  if(HdwCfg.DmaPresent)

    printf("\nDMA present");

  printf("\nNubber of COM ports: %d", HdwCfg.NumberOfCom);

 

  if(HdwCfg.GamePresent)

    printf("\nGame adapter present");

  if(HdwCfg.JrComPresent)

    printf("\nPCjr Com present");

  printf("\nNumber of LPT ports: %d", HdwCfg.NumberOfLpt);

  // Вызываем прерывание INT 12h для определения

  // объема основной оперативной памяти компьютера

  rg.h.ah = 0x0;

  int86(0x12, &rg, &rg);

  // Выводим объем оперативной памяти

  printf("\nRAM istalled: %d Kbytes",

    (unsigned int)rg.x.ax);

  // Получаем объем расширенной оперативной памяти,

  // доступной через прерывание INT 15h

  rg.h.ah = 0x88;

  int86(0x15, &rg, &rg);

  // Выводим объем расширенной оперативной памяти

  printf("\nExtended RAM istalled: %ld Kbytes",

    (unsigned int)rg.x.ax);

  getch();

  return 0;

}


Программа IOSOUND


Приведем исходный текст программы IOSOUND, генерирующую звук без использования таймера (листинг 5.3.). Эта программа формирует импульсы при помощи манипуляций с разрядом 1 порта 61h.

Листинг 5.3. Файл iosound\iosound.с

// =====================================================

// Генерация звукового сигнала через порты таймера

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <dos.h>

#define FREQUENCY 200

#define CYCLES 30000

int main(void)

{

  // Во время генерации звука прерывания должны

  // быть запрещены.

  _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);

 

  return 0;

}

Так как в программе IOSOUND для формирования полупериодов используется задержка с помощью команды LOOP, высота генерируемого тона будет зависеть от производительности системы.

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



Программа KBDASCII


Приведем исходные тексты программы KBDASCII, отображающей на экране коды ASCII и расширенные коды ASCII нажимаемых клавиш (листинг2.4).

Листинг 2.4. Файл kbdascii\kbdascii.c

// =====================================================

// Просмотр клавиатурных кодов ASCII

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <conio.h>

#include <ctype.h>

#include <stdio.h>

int main(void)

{

  int key;

 

  printf("KBDASCII, (c) A. Frolov, 1997\n"

    "Press any key, <Esc> to exit\n");

 

  // Читаем в цикле символы с клавиатуры и отображаем

  // коды ASCII нажатых клавиш.

  // Выходим из цикла, когда пользователь нажимает

  // клавишу <Esc>

  for(;;)

  {

    // Читаем символ

    key = getch();

    // Если прочитанный символ равен 0, вызываем функцию

    // getch для получения расширенного

    // кода ASCII нажатой клавиши

    if((key == 0) (key == 0xe0))

    {

      key = getch();

      printf("Extended code ASCII:\t" );

    }

    else

      printf( "Code ASCII:\t");

   

    printf("%d\n",key);

    // Когда пользователь нажимает клавишу

    // <Esc>. выходим из цикла

    if(key == 27)

      break;

  }

  return 0;

}



Программа KBDHIT


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

Листинг 2.5. Файл kbdhit\kbdhit.c

// =====================================================

// Демонстрация применения функции kbhit

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

int main(void)

{

  int key;

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

  // Во время ожидания выводим на экран поочередно

  // символы "<" и ">"

  while(!kbhit())

    printf("<\b>\b");

  // Как только будет нажата какая-нибудь клавиша,

  // выводим ее ASCII-код

  key = getch();

  // Если прочитанный символ равен 0, вызываем

  // функцию getch() для получения расширенного

  // кода ASCII нажатой клавиши

  if( (key == 0) (key == 0xe0) )

  {

    key = getch();

    printf( "Extended code ASCII:\t" );

  }

  else

    printf( "Code ASCII:\t");

 

  printf("%d\n",key);

  return 0;

}



Программа KBDLED


Приведем пример простейшей программы KBDLED, управляющей светодиодами, расположенными на лицевой панели клавиатуры (листинг 2.1). Такое управление может выполняться только при использовании порта 60h, так как BIOS не содержит никаких подходящих для этого функций. Наша программа после запуска включит все светодиоды и будет ожидать, пока вы не нажмете любую клавишу. Затем программа выключит светодиоды.

Заметим, что программа KBDLED может не работать на виртуальной машине DOS, создаваемой, например, в операционной системе Microsoft Windows NT.

Листинг 2.1. Файл kbdled\kbdled.c

// =====================================================

// Переключение светодиодов на клавиатуре

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

int main(void)

{

  int i;

  printf("KBDLED, (c) A. Frolov, 1997\n");

 

  // Посылаем процессору клавиатуры

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

  outp(0x60,0xed);

  // Перед посылкой второго байта команды

  // выполняем небольшую задержку

  for(i=0; i<4000; i++);

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

  // которого определяют состояние светодиодов

  // на лицевой панели клавиатуры

  outp(0x60,7);

  printf("Нажмите любую клавишу для выключения "

         "светодиодов\n");

 

  // Задерживаем выполнение программы, пока

  // пользователь не нажмет на любую клавишу

  getch();

  // Выключаем все светодиоды

  outp(0x60,0xed);

  for(i=0; i<4000; i++);

  outp(0x60,0);

 

  return 0;

}



Программа KBDSCAN


Для демонстрации использования функции 00h прерывания INT16h мы подготовили программу, выводящую на экран скан-коды и коды ASCII нажимаемых клавиш (листинг 2.2).

Листинг 2.2. Файл kbdscan\kbdscan.c

// =====================================================

// Просмотр клавиатурных скан-кодов и кодов ASCII

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <dos.h>

int main(void)

{

  union REGS  rg;

 

  printf("KBDSCAN, (c) A. Frolov, 1997\n"

    "Press <ESC> to exit\n");

  for(;;)

  {

    // Вызываем прерывание INT 16h

    rg.h.ah = 0;

    int86(0x16, &rg, &rg);

    // Выводим на экран содержимое регистров AH и AL,

    // содержащих, соответственно, скан-код и код ASCII

    // нажатой клавиши

    printf("\nScan = %02.2X Ascii = %02.2X",

      rg.h.ah, rg.h.al);

    // Если была нажата клавиша ESC, завершаем работу

    // программы

    if(rg.h.ah == 1)

      break;

  }

  return 0;

}



Программа MSCURSOR


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

Листинг 3.1. Файл mscursor\mscursor.c

// =====================================================

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

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <dos.h>

#include <stdio.h>

#include <conio.h>

int main(void)

{

  int nButtons;

  union REGS rg;

  printf("MSCURSOR, (c) A. Frolov, 1997\n");

 

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

  rg.x.ax = 0;

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

 

  if(rg.x.bx == 0)

  {  

    printf("Mouse not found");

    return -1;

  }

   

  // Сохраняем количество клавиш

  nButtons = rg.x.bx;

 

  printf("Mouse type: ");

      

  switch (nButtons)

  {

    case 2:

    {

         printf("2-button mouse\n");

         break;

       }

       case 3:

       {

         printf("2-button Mouse Systems\n");

         break;

       }

       case 0:

       {

         printf("Unknown type %d\n", nButtons);

         break;

       }

      

       default:

       {

         printf("Unknown type %d\n", nButtons);

         break;

       }

  }

  // Включаем курсор и ожидаем, пока пользователь

  // нажмет на клавишу

  rg.x.ax = 1;

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

 

  printf("Mouse cursor on. Press any key\n");

  getch();

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

  rg.x.ax = 2;

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

  printf("Mouse cursor off. Press any key\n");

  getch();

  return 0;

}



Программа MSDRIVER


Программа MSDRIVER иллюстрирует способ работы с драйвером событий.

Исходный текст драйвера событий, составленный на языке ассемблера, представлен в листинге 3.5.

Листинг 3.5. Файл msdriver\handler.asm

;**

;.Name       ms_handl

;.Title      Драйвер событий

;

;.Descr      Драйвер событий вызывается драйвером мыши,

;            когда происходит какое-нибудь событие из числа

;            заданных при установке драйвера событий.

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

;            пользователя, ее вызывает только драйвер мыши.

;

;.Proto      void far ms_handl(void);

;

;.Params     Не используются

;**

       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, и т.д.

Исходный текст программы MSDRIVER представлен в листинге 3.6.

Листинг 3.6. Файл msdriver\msdriver.с

// =====================================================

// Работа с драйвером событий

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <dos.h>

#include <stdio.h>

#include <conio.h>

union REGS reg;

struct SREGS segregs;

extern void far MS_HANDL(void);

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

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

// запишет в эту переменную значение 1

unsigned MS_FLAG;

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

// в драйвер событий.

unsigned MS_BX;

unsigned MS_CX;

unsigned MS_DX;

unsigned MS_SI;

unsigned MS_DI;

unsigned MS_DS;

int main (void)

{

  MS_FLAG=0;

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

  reg.x.ax = 0;

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

 

  if(reg.x.bx == 0)

  {  

    printf("Mouse not found\n");

    return -1;

  }

  // Подключаем драйвер событий, устанавливаем маску таким

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

  // или правую клавиши мыши

  ms_seth(2 | 8, MS_HANDL);

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

  reg.x.ax = 1;

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

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

  for(;;)

  {

    if(MS_FLAG)

    {

      printf("\nRegisters on driver entry:"

        "\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("\nPress any key...");

        getch();

        return 0;

     }

  }

}

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);

}


Программа MSGCURS


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

Листинг 3.2. Файл msgcurs\msgcurs.c

// =====================================================

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

// в разных видеорежимах

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <dos.h>

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

union REGS rg;

int main(void)

{

  int i;

  unsigned old_videomode, new_videomode;

  char buf[20], *bufptr;

  int nButtons;

 

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

  rg.x.ax = 0x0f00;

  int86(0x10, &rg, &rg);

  old_videomode = rg.h.al;

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

  buf[0] = 10;

  printf("Enter new video mode: ");

  bufptr = cgets(buf);

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

  new_videomode = atoi(bufptr);

  rg.h.ah = 0;

  rg.h.al = new_videomode;

  int86(0x10, &rg, &rg);

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

  rg.x.ax = 0;

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

 

  if(rg.x.bx == 0)

  {  

    printf("Mouse not found\n");

    return -1;

  }

   

  // Сохраняем количество клавиш

  nButtons = rg.x.bx;

  printf("Mouse type: %d\n", nButtons);

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

  rg.x.ax = 1;

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

 

  printf("Mouse cursor on. Press any key\n");

  getch();

  while(!kbhit())

  {

    rg.x.ax = 3;

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

   

    printf("%2d x:%5d y:%5d",

         rg.x.bx, rg.x.cx, rg.x.dx);

       for(i=0; i<18; i++) printf("\b");

  }

  getch();

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

  rg.x.ax = 2;

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

  // Восстанавливаем режим видеоадаптера

  rg.h.ah = 0;

  rg.h.al = old_videomode;

  int86(0x10, &rg, &rg);

 

  return 0;

}

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

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



Программа MSGFORM


Мы подготовили исходный текст программы MSGFORM, изменяющий форму курсора в графическом режиме (листинг 3.3).

Листинг 3.3. Файл msgform\msgform.c

// =====================================================

// Изменение формы курсора в графическом режиме

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <dos.h>

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

union REGS reg;

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

unsigned 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

};

int main(void)

{

  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("Enter new video mode: ");

  bufptr = cgets(buf);

  new_videomode = atoi(bufptr);

  reg.h.ah = 0;

  reg.h.al = new_videomode;

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

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

  reg.x.ax = 0;

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

 

  if(reg.x.bx == 0)

  {  

    printf("Mouse not found\n");

    return -1;

  }

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

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

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

  reg.x.ax = 1;

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

  getch();

  reg.h.ah = 0;

  reg.h.al = old_videomode;

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

  return 0;

}

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

{

  struct SREGS segregs;

 

  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);

}



Программа MSTFORM


Приведем исходный текст программы MSTFORM (листинг 3.4), создающую курсор в виде вертикальной стрелки, направленной вверх, на синем фоне.

Листинг 3.4. Файл mstform\mstform.c

// =====================================================

// Изменение формы курсора в текстовом режиме

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <dos.h>

#include <stdio.h>

#include <conio.h>

union REGS reg;

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

int main(void)

{

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

  reg.x.ax = 0;

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

 

  if(reg.x.bx == 0)

  {  

    printf("Mouse not found\n");

    return -1;

  }

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

  ms_tform(0, 0, 0x1418);

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

  reg.x.ax = 1;

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

  getch();

  return 0;

}

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);

}



Приведем пример самой простой программы


Приведем пример самой простой программы NPU1 (листинг 10.1), которая выполняет вычисления по следующей несложной формуле:

z = x + y;

В этой программе значения x и y задаются в виде констант.

Листинг 10.1. Файл npu1\npu1.asm

; =====================================================

; Простейшая программа для работы с арифметическим

; сопроцессором

;

; (C) A. Frolov, 1997

;

; E-mail: frolov@glas.apc.org

; WWW:    http://www.glasnet.ru/~frolov

;            or

;         http://www.dials.ccas.ru/frolov

; =====================================================

  .model small

  .STACK  100h

  .DATA

; Здесь находятся константы с одинарной

; точностью x и y

x  dd 1.0

y  dd 2.0

; Резервируем четыре байта для результата

z  dd ?

  .CODE

begin:

  mov ax, DGROUP

  mov ds, ax

; Записываем в стек численных регистров

; значение x

  fld    x

; Складываем содержимое верхушки стека

; с константой y

  fadd   y

; Записываем результат в ячейку z

  fstp   z

; Завершаем работу программы и

; возвращаем управление операционной системе

  mov ax, 4C00h

  int 21h

 

  END begin

Как убедиться в том, что программа работает правильно?

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

Запустим отладчик CodeView, передав ему в качестве параметра имя приведенной выше программы:

cv npu1.exe

После того, как отладчик запустится, откройте окно регистров сопроцессора. В нижней части экрана появится окно регистров сопроцессора, показанное на рис. 10.20.



Рис.10.20. Окно регистров сопроцессора

Пусть вас не смущает то, что в этом окне пока не показывается состояние регистров сопроцессора. Нажмите клавишу F8, выполнив один шаг программы.

Теперь вы видите содержимое регистров управления и состояния (cControl, cStatus), регистра тегов (cTag), регистров указателей команд и данных (Instr Ptr, Data Ptr), код выполняемой команды (Opcode).
Отображается также содержимое стека численных регистров (Stack), но пока это поле пустое, так как все численные регистры отмечены в регистре тегов как пустые.

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

Теперь в области регистров стека показано содержимое регистра cST(0), причем как в двоичном виде, так и с использованием экспоненциальной (научной) нотации. Как и следовало ожидать, регистр ST(0) содержит величину 1.0 (рис. 10.21).



Рис. 10.21. В регистре ST(0) находится значение 1.0

Выполним еще одну команду, прибавляющую к содержимому ST(0) значение 2.0 из переменной y. Теперь регистр ST(0) содержит величину 3.0 (рис. 10.22).



Рис. 10.22. Теперь в регистре ST(0) находится значение 3.0

Последняя команда выталкивает из стека хранящееся там значение (3.0) и записывает его в переменную z. Теперь стек численных регистров снова пуст.


Программа PRINTFL


Приведем исходный текст программы PRINTFL, которая распечатывает содержимое файла с использованием функции 0 прерывания INT 17h (листинг 7.1).

Листинг 7.1. Файл printfl\printfl.с

// =====================================================

// Печать на принтере с помощью функций BIOS

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <dos.h>

#include <stdio.h>

#include <conio.h>

union REGS rg;

int printchar(int chr);

int error(char chr, int status);

int main(int argc, char *argv[])

{

  FILE *srcfile;

  // Открываем файл, заданный первым параметром

  // в командной строке.

  // Если при запуске программы оператор забыл

  // указать имя файла, выводим напоминающее сообщение

  if( (srcfile = fopen( argv[1], "rb" )) == NULL )

  {

    printf("\nЗадайте имя файла в качестве параметра");

    return (-1);

  }

  // Читаем файл по одному символу, полученный из файла

  // символ выводим на принтер при помощи функции printchar

  for(;;)

  {

    printchar(fgetc(srcfile));

    if(feof(srcfile))

      break;

  }

  // Закрываем файл

  fclose(srcfile);

 

  return 0;

}

// ------------------------------------

// Эта функция выводит один символ

// на первый принтер (LPT1)

// ------------------------------------

int printchar(int chr)

{

  int status;

  // Повторяем в цикле выдачу символа на принтер

  // до тех пор, пока он не будет выведен без

  // ошибок, либо пока оператор не отменит

  // распечатку файла

  for(;;)

  {

    // Дублируем распечатываемый символ на экране

    putch(chr);

    // Вызываем функцию 00h прерывания INT 17h -

    // распечатка символа на принтере.

    // В регистре DX задаем номер принтера LPT1 - это 0

    rg.h.ah = 0;

    rg.h.al = chr;


    rg.x.dx = 0;

    int86(0x17, &rg, &rg);

    // Запоминаем байт состояния принтера

    // после вывода символа

    status = rg.h.ah;

    // Проверяем наличие ошибок. Нас интересуют биты:

    //

    //    0 - задержка при печати

    //    3 - ошибка ввода/вывода

    //    4 - принтер в состоянии ONLINE (1) или OFFLINE (0)

    //    5 - конец бумаги

    if((status & 0x39) != 0x10)

    {

      // Вызываем функцию обработки ошибки error(). Эта

      // функция возвращает 0, если оператор желает

      // повторить печать символа, или 1 - если

      // оператор отменяет печать

      if(error(chr, status))

      {

        printf("\nПечать завершилась аварийно");

        return -1;

      }

    }

    else

      break;

  }

}

// ------------------------------------

// Функция выводит на экран состояние

// принтера и запрашивает у оператора

// требуемые действия - повторить

// печать символа или отменить печать

// ------------------------------------

int error(char chr, int status)

{

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

  printf("\nОшибка принтера %02.2X"

       "\n\nСостояние принтера:"

       "\n-------------------", status);

  if(status & 1)

    printf("\nТаймаут при печати");

  if(status & 8)

    printf("\nОшибка ввода/вывода");

  if(!(status & 0x10))

    printf("\nПринтер находится в состоянии OFFLINE");

  if(status & 0x20)

    printf("\nКонец бумаги");

  printf("\n\nДля отмены печати нажмите клавишу ESC,"

    "\nдля повтора - любую другую клавишу\n");

  if(getch() == 27)

    return 1;

  else

    return 0;

}

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

После вызова прерывания INT 17h функция printchar проверяет состояние принтера.При возникновении ошибки ввода/вывода вызывается обработчик - функция error. Эта функция выводит на экран состояние принтера (в развернутом виде с объяснением каждого бита в байте состояния), а также  запрашивает пользователя о дальнейших действиях.

Если пользователь может устранить причину ошибки (перевести принтер в состояние online, вставить бумагу, если она кончилась и так далее), он нажимает любую клавишу, кроме <Esc>. В этом случае функция error возвращает 0. Иначе возвращается значение 1.

Если пользователь решил повторить печать, и, соответственно, если функция error возвратила значение 0, функция printchar повторяет печать символа. При отмене печати выдается сообщение об ошибке и работа программы завершается.


Программа RANDOM


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

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

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

Листинг 5.4. Файл random\random.с

// =====================================================

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

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <dos.h>

void rnd_set(int bound);

int rnd_get(void);

int main(void)

{

  int i, j;

  printf("\nГенератор случайных чисел."

    "\nНажмите любую клавишу,"

       "\nдля завершения работы нажмите <Control+C>"

       "\n");

  for(;;)

  {

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

    // чисел и инициализируем таймер

    rnd_set(80);

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

    getch();

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

    // случайное число

    j = rnd_get();

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

    // длина которой равна полученному случайному числу

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

      putchar(219);

    printf("\n");

  }

  return 0;

}

/**

*.Name         rnd_set

*.Title        Инициализация генератора случайных чисел

*

*.Descr        Эта функция инициализирует канал 2 таймера

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

*              случайных чисел

*

*.Proto        void rnd_set(int bound)


*

*.Params       int bound - верхняя граница для генерируемых

*                          случайных чисел.

**/

void rnd_set(int bound)

{

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

  outp(0x43, 0xb6);

  // Загружаем регистр счетчика таймера - сначала

  // младший, затем старший байты

  outp(0x42, bound & 0x00ff);

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

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

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

}

/**

*.Name         rnd_get

*.Title        Получение от таймера случайного числа

*

*.Descr        Эта функция получает случайное число от

*              таймера, который был предварительно

*              проинициализирован функцией rnd_set

*

*.Proto        int rnd_get(void)

*

*.Params       Отсутствуют.

*

*.Return       Случайное число в диапазоне от 0, до

*              уменьшенного на 1 значения, заданного в

*              качестве параметра функции rnd_set().

**/

int rnd_get(void)

{

  int i;

 

  // Выдаем команду CLC для фиксирования

  // текущего значения регистра канала 2 таймера

  outp(0x43, 0x86);

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

  i = inp(0x42);

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

 

  return(i);

}


Программа RTCALARM


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

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

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

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

Листинг 4.1. Файл rtcalarm\rtcalarm.с

// =====================================================

// Работа с часами реального времени

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#include <dos.h>

typedef struct _SYSTIMER_

{

  char hour;             // часы

  char min;              // минуты

  char sec;              // секунды

  unsigned year;         // год

  char month;            // месяц

  char day;              // число

  char daylight_savings; // флаг летнего времени

} SYSTIMER;

#define RTC_GET_TIME     2 // прочитать показания часов;

#define RTC_SET_TIME     3 // установить часы;

#define RTC_GET_DATE     4 // прочитать дату;

#define RTC_SET_DATE     5 // установить дату;

#define RTC_SET_ALARM    6 // установить будильник;

#define RTC_CLEAR_ALARM  7 // сбросить будильник.


int bcd1bin(char *bcd);

int bcd2bin(char *bcd);

void bin1bcd(int bin, char *bcd);

void _interrupt _far alarm(void);

int timer(char fn, SYSTIMER *tm);

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

#pragma check_stack( off )

#pragma check_pointer( off )

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

#define BEEP() _asm { \

       _asm mov bx,0 \

       _asm mov ax, 0E07h \

       _asm int 10h \

}

// Прототип программы-обработчика прерывания

// будильника

void _interrupt _far alarm(void);

// Переменная для хранения старого

// вектора будильника

void (_interrupt _far *old_4a)(void);

union REGS reg;

int 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((char*)&(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);

 

  return 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

// ----------------------------------

void 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();

}

/**

*.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   - сбросить будильник.

*

*              SYSTIMER tm - структура, содержащая данные

*                            для установки часов или

*                            показания часов:

*

*.Return       0   - успешное выполнение функции;

*              -1  - часы реального времени отсутствуют

*                    в компьютере;

**/

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;

}


Программа TESTHMA


Первая программа с названием TESTHMA (листинг 11.1) демонстрирует проверку подключения драйвера и использование его основных функций.

Листинг 11.1. Файл testhma\testhma.asm

; =====================================================

; Вызов основных функций API драйвера HIMEM.SYS

;

; (C) A. Frolov, 1997

;

; E-mail: frolov@glas.apc.org

; WWW:    http://www.glasnet.ru/~frolov

;            or

;         http://www.dials.ccas.ru/frolov

; =====================================================

@@out_ch MACRO c1,c2,c3,c4,c5,c6,c7,c8,c9,c10

  mov   ah,02h

  IRP   chr,<c1,c2,c3,c4,c5,c6,c7,c8,c9,c10>

  IFB   <chr>

  EXITM

  ENDIF

  mov   dl,chr

  int   21h

  ENDM

  ENDM

@@out_str MACRO

  mov   ah,9

  int   21h

  ENDM

BEEP  MACRO

  mov bx,0

  mov ax, 0E07h

  int 10h

  ENDM

  .model small

  .STACK  100h

  .DATA

msg    DB 13,10,"HIMEM.SYS API Demo", 13, 10

       DB "(C) Frolov A., 1997",13,10,13,10

       DB "$"

noHMM     DB 13,10

          DB "HIMEM.SYS not installed",13,10,"$"

yesHMM    DB 13,10,"HIMEM.SYS istalled, ", "$"

ver1      DB "version: ", "$"

ver2      DB ", modification: ", "$"

errmsg    DB 13,10,"Error code ", "$"

okmsg     DB 13,10,"Success!", "$"

hmareq    DB 13,10,"Request HMA", "$"

hmarel    DB 13,10,"Release HMA", "$"

enA20     DB 13,10,"Open A20", "$"

dsA20     DB 13,10,"Close A20", "$"

loc_enA20 DB 13,10,"Local open A20","$"

loc_dsA20 DB 13,10,"Local close A20", "$"

check_A20 DB 13,10,"Check A20", "$"

free_ext_mem DB 13,10,"Extended memory, Kbyte: ", "$"

max_ext_block DB 13,10,"Max free Extended memory block, Kbyte: ", "$"


HMMEntry dd ?

  .CODE

begin:

  mov ax, DGROUP

  mov ds, ax

  mov ah, 9h ; Выводим заголовок

  mov dx, OFFSET msg

  int 21h

; Проверяем, установлен ли драйвер HIMEM.SYS

  mov ax, 4300h

  int 2fh

  cmp al, 80h

  je  HMM_installed

; Если не установлен, выводим сообщение и завершаем

; работу программы

  mov ah, 9h

  mov dx, OFFSET noHMM

  int 21h

  jmp terminate

HMM_installed:

  mov ah, 9h

  mov dx, OFFSET yesHMM

  int 21h

; Получаем адрес управляющей функции драйвера

  mov ax, 4310h

  int 2fh

  mov word ptr cs:[HMMEntry][0], bx

  mov word ptr cs:[HMMEntry][2], es

; Получаем номер версии

  mov  ah, 9h

  mov  dx, OFFSET ver1

  int  21h

  mov  ax,0

  call cs:[HMMEntry]

; Выводим номер версии на экран

  call  Print_word

  mov   ah, 9h

  mov   dx, OFFSET ver2

  int   21h

  mov   ax, bx

  call  Print_word

; Запрашиваем область HMA

  mov   ah, 9h

  mov   dx, OFFSET hmareq

  int   21h

  mov   ax,0100h

  mov   dx,0ffffh

  call  cs:[HMMEntry]

  or    ax, ax

  jnz   hmareq_ok

  jmp   error

hmareq_ok:

  mov   ah, 9h

  mov   dx, OFFSET okmsg

  int   21h

; Открываем линию A20

  mov   ah, 9h

  mov   dx, OFFSET enA20

  int   21h

  mov   ax,0300h

  call  cs:[HMMEntry]

  or    ax, ax

  jnz   enA20_ok

  jmp   error

enA20_ok:

  mov   ah, 9h

  mov   dx, OFFSET okmsg

  int   21h

; Закрываем линию A20

  mov   ah, 9h

  mov   dx, OFFSET dsA20

  int   21h

  mov   ax,0400h

  call  cs:[HMMEntry]

  or    ax, ax

  jnz   dsA20_ok

  jmp   error

dsA20_ok:

  mov   ah, 9h

  mov   dx, OFFSET okmsg

  int   21h

; Освобождаем область HMA

  mov   ah, 9h

  mov   dx, OFFSET hmarel

  int   21h

  mov   ax,0200h

  call  cs:[HMMEntry]



 

  or    ax, ax

  jz    error

  mov   ah, 9h

  mov   dx, OFFSET okmsg

  int   21h

; Получаем локальный доступ к линии A20

  mov   ah, 9h

  mov   dx, OFFSET loc_enA20

  int   21h

  mov   ax,0500h

  call  cs:[HMMEntry]

  or    ax, ax

  jz    error

  mov   ah, 9h

  mov   dx, OFFSET okmsg

  int   21h

; Проверяем линию A20

  mov   ah, 9h

  mov   dx, OFFSET check_A20

  int   21h

  mov   ax,0700h

  call  cs:[HMMEntry]

  or    ax, ax

  jz    error

  mov   ah, 9h

  mov   dx, OFFSET okmsg

  int   21h

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

  mov   ah, 9h

  mov   dx, OFFSET free_ext_mem

  int   21h

  mov   ax,0800h

  call  cs:[HMMEntry]

  push  ax

  mov   ax, dx

  call  Print_word

  mov   ah, 9h

  mov   dx, OFFSET max_ext_block

  int   21h

  pop   ax

  call  Print_word

; Освобождаем линию A20

  mov   ah, 9h

  mov   dx, OFFSET loc_dsA20

  int   21h

  mov   ax,0600h

  call  cs:[HMMEntry]

  or    ax, ax

  jz    error

  mov   ah, 9h

  mov   dx, OFFSET okmsg

  int   21h

  jmp   terminate

error:

  push  bx

  mov   ah, 9h

  mov   dx, OFFSET errmsg

  int   21h

  pop   ax

  call  Print_word

terminate:

; Завершаем работу программы и

; возвращаем управление операционной системе

  mov ax, 4C00h

  int 21h

; Вывод на экран содержимого регистра AX

Print_word proc near

;--------------------

  push ax

  push bx

  push dx

  push ax

  mov cl,8

  rol ax,cl

  call Byte_to_hex

  mov bx,dx

  @@out_ch bh

  @@out_ch bl

  pop ax

  call Byte_to_hex

  mov bx,dx

  @@out_ch bh

  @@out_ch bl

  pop dx

  pop bx

  pop ax

  ret

Print_word endp

Byte_to_hex proc near

;--------------------

; al - input byte

; dx - output hex

;--------------------

  push ds

  push cx

  push bx

  lea bx,tabl

  mov dx,cs

  mov ds,dx

  push ax

  and al,0fh

  xlat

  mov dl,al

  pop ax

  mov cl,4

  shr al,cl

  xlat

  mov dh,al

  pop bx

  pop cx

  pop ds

  ret

tabl db '0123456789ABCDEF'

Byte_to_hex endp

 

  END begin


Программа TIMERST


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

Листинг 5.1. Файл timerst\timerst.с

// =====================================================

// Просмотр слова состояния и содержимого

// счетчиков таймера

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

int 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-------\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);

  return 0;

}



Программа TMSOUND


Программа TMSOUND (листинг 5.2) проигрывает мелодию с помощью системного таймера.

Листинг 5.2. Файл tmsound\tmsound.с

// =====================================================

// Проигрывание музыки с помощью таймера

//

// (C) Фролов А.В, 1997

//

// E-mail: frolov@glas.apc.org

// WWW:    http://www.glasnet.ru/~frolov

//            или

//         http://www.dials.ccas.ru/frolov

// =====================================================

#include <stdio.h>

#include <conio.h>

#include <dos.h>

void sound(int, int);

void tm_sound(int freq, int time);

void tm_delay(int ticks);

// Массив частот для мелодии

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

};

int main(void)

{

  int i;

  for(i=0 ;mary[i] != 0 ;i++)

    tm_sound(mary[i], del[i]);

 

  return 0;

}

/**

*.Name         tm_sound

*.Title        Формирование тона заданной длительности

*

*.Descr        Эта функция предназначена для генерации

*              на громкоговорителе тона заданной

*              длительности и частоты

*

*.Proto        void tm_sound(int freq, int time);

*

*.Params       int freq - частота в герцах;

*              int time - длительность в периодах работы

*               таймера

**/

void tm_sound(int freq, int time)

{

  int cnt;

  // Задаем режим канала 2 таймера

  outp(0x43, 0xb6);

  // Вычисляем задержку для загрузки в

  // регистр счетчика таймера

  cnt = (int)(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);

}

/**

*.Name         tm_delay

*.Title        Формирование задержки по таймеру

*

*.Descr        Эта функция формирует задержку, используя

*              системный таймер

*

*.Proto        void tm_delay(int ticks)

*

*.Params       int ticks - величина задержки в периодах работы таймера

**/

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

  }

}


Программирование асинхронного адаптера


К сожалению, среди функций программного интерфейса MS-DOS нет ни одной, обеспечивающей сколько-нибудь серьезную работу с последовательным асинхронным адаптером. Две функции прерывания INT21h с номерами 3 и 4 предназначены для чтения и записи байтов через асинхронный адаптер. Обе эти функции имеют дело с адаптером COM1 или AUX. Функция 3 получает в регистре AL символ, принятый из адаптера, функция 4 посылает в адаптер символ, записанный в регистр DL.

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

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

Учитывая все это, для программирования асинхронного адаптера мы рекомендуем использовать порты ввода/вывода микросхемы UART.



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


В этом разделе мы расскажем о некоторых приемах программирования принтеров.

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

Больше всего распространены две группы матричных принтеров, различающихся по системе используемых команд - это принтеры, совместимые с принтерами Epson и принтеры, совместимые с принтерами IBM Proprinter. Принтеры некоторых третьих фирм-производителей компьютерного оборудования обычно эмулируют команды обоих или одной из этих групп в зависимости от установленного режима работы.

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

Средим матричных принтеров в России очень распространены принтеры серии Epson FX: FX-80, FX-850, FX-1050. Печатающие головки этих принтеров имеют девять иголок, поэтому качество печати принтеров серии FX оставляет желать лучшего. Принтеры серии Epson LQ используют для печати 24 иголки, кроме того, некоторые модели способны печатать цветные изображения (например, Epson LQ-2550). Качество печати принтеров LQ намного лучше.



Программирование режимов работы принтера


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

Признак начала командной последовательности символов - байт ESC с кодом 1Bh. Вслед за этим байтом программа посылает в принтер байты командной последовательности. Длина последовательности зависит от выполняемой команды.

Первый байт командной последовательности - код выполняемой команды. Далее следуют байты параметров команды. Некоторым командам не предшествует байт ESC (это, например, команды перевода строки, страницы или команды табуляции).

Подробное описание всех команд не входит в задачу данной книги. Вы можете найти список команд в документации на принтер. Мы опишем подробно лишь несколько команд принтера Epson FX-850/1050 с целью иллюстрации способов программирования режимов принтера с использованием протокола ESC/P.