Порт 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, ®, ®);
if(reg.x.bx == 0)
{
printf("Mouse not found\n");
return -1;
}
// Подключаем драйвер событий, устанавливаем маску таким
// образом, чтобы драйвер вызывался при нажатии на левую
// или правую клавиши мыши
ms_seth(2 | 8, MS_HANDL);
// Включаем курсор
reg.x.ax = 1;
int86(0x33, ®, ®);
// Ожидаем вызова драйвера событий.
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,®,®,&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, ®, ®);
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.x.ax = 0;
int86(0x33, ®, ®);
if(reg.x.bx == 0)
{
printf("Mouse not found\n");
return -1;
}
// Задаем новую форму для курсора мыши
ms_gform(0,0, &form[0]);
// Включаем курсор
reg.x.ax = 1;
int86(0x33, ®, ®);
getch();
reg.h.ah = 0;
reg.h.al = old_videomode;
int86(0x10, ®, ®);
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,®,®,&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, ®, ®);
if(reg.x.bx == 0)
{
printf("Mouse not found\n");
return -1;
}
// Задаем новую форму для курсора мыши
ms_tform(0, 0, 0x1418);
// Включаем курсор
reg.x.ax = 1;
int86(0x33, ®, ®);
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,®,®);
}
Приведем пример самой простой программы
Приведем пример самой простой программы 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,®,®);
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.