еле B:, нужна строка: COMMAND_LINE DB 12,'B: /C DIR A:',13 Следующий кусочек кода устанавливает адрес командной строки в блок параметров, используемый в примере [1.3.2]: LEA BX,PARAMETERS ;получение адреса блока пар-ров MOV AX,OFFSET COMMAND_LINE ;получение смещения ком. строки MOV [BX]+2,AX ;пересылка в 1-е 2 байта блока MOV AX,SEG COMMAND_LINE ;получение сегмента ком. строки MOV [BX]+4,AX ;пересылка во 2-е 2 байта блока 1.3.4 Сохранение программы в памяти после завершения. Программы, оставленные резидентными в памяти, могут служить в качестве утилит для других программ. Обычно такие программы вызы- ваются через неиспользуемый вектор прерывания. MS DOS рассматри- вает такие программы как часть операционной системы, защищая их от наложения других программ, которые будут загружены впоследст- вии. Резидентные программы обычно пишутся в форме COM, что обсуж- дается в пункте [1.3.6]. Программы, написанные в форме EXE оста- вить резидентными в памяти немного труднее. Завершение программы прерыванием 27H оставляет ее резидентной в памяти. CS должен указывать на начало PSP для того, чтобы эта функция работала правильно. В программах COM, CS сразу устанавли- вается соответствующим образом, поэтому надо просто завершить программу прерыванием 27H. В программах EXE , CS первоначально указывает на первый байт, следующий за PSP (т.е. 100H). При нор- мальном завершении EXE программы последняя инструкция RET вытал- кивает из стека первые положенные туда значения: PUSH DX / MOV AX,0 / PUSH AX. Поскольку DS первоначально указывает на начало PSP, то при получении этих значений из стека счетчик команд ука- зывает на смещение 0 в PSP, где при инициализации записывается инструкция INT 20H. Поэтому INT 20H выполняется, а это стандарт- ная функция для завершения программы и передачи управления в DOS. На рис. 1-5 показан этот процесс. Чтобы заставить прерывание 27H работать в EXE программе надо поместить 27H во второй байт PSP (первый содержит машинный код инструкции INT), а затем завершить программу обычным RET. Для обоих типов файлов прежде чем выпол- нить прерывание 27H, DX должен содержать смещение конца програм- мы, отсчитываемое от начала PSP. Средний уровень. Вектор прерывания устанавливается с помощью функции 25H преры- вания 21H, как показано в [1.2.3] (здесь используется вектор 70H). Позаботьтесь, чтобы процедура оканчивалась IRET. Кроме самой процедуры, устанавливаемая программа не должна делать ниче- го, кроме инициализации вектора прерывания, присвоения DX значе- ния смещения конца процедуры и завершения. Для COM файлов просто поместите оператор INT 27H в конец программы. Для EXE файлов поместите этот оператор в первое слово PSP и завершите программу обычным оператором RET. Для того чтобы выполнить процедуру, впос- ледствии загруженная программа должна вызвать INT 70H. Приведены примеры для обоих типов файлов (COM и EXE). В обоих установлена метка FINISH для отметки конца процедуры прерывания (напоминаем, что знак $ дает значение счетчика команд в этой точке). Для COM файлов FINISH дает смещение от начала PSP, как и требуется для прерывания 27H. Для EXE файлов смещение отсчиты- вается от первого байта, следующего за PSP, поэтому к нему необ- ходимо прибавить 100H, чтобы пересчитать на начало PSP. Заметим, что поместив процедуру в начало программы, мы можем исключить установочную часть кода из резидентной порции. Другой возможный фокус состоит в использовании инструкции MOVSB для пересылки кода процедуры вниз в неиспользуемую часть PSP, начиная со смещения 60H, что освобождает 160 байт памяти. Случай файла COM: ;---здесь процедура прерывания BEGIN: JMP SHORT SET_UP ;переход на установку ROUTINE PROC FAR PUSH DS ;сохранение регистров . (процедура) . POP DS ;восстановление регистров IRET ;возврат из прерывания FINISH EQU $ ;отметка конца процедуры ROUTINE ENDP ;---установка вектора прерывания SET_UP: MOV DX,OFFSET ROUTINE ;смещение процедуры в DX MOV AL,70H ;номер вектора прерывания MOV AH,25H ;функция установки вектора INT 21H ;устанавливаем вектор ;---завершение программы, оставляя резидентной LEA DX,FINISH ;определяем треб. смещение INT 27H ;завершение Случай файла EXE: ;---здесь резидентная процедура JMP SHORT SET_UP ;переход на установку ROUTINE PROC FAR PUSH DS ;сохранение регистров . (процедура) . POP DS ;восстановление регистров IRET ;возврат из прерывания FINISH EQU $ ;отметка конца процедуры ROUTINE ENDP ;---установка вектора прерывания SET_UP: MOV DX,OFFSET ROUTINE ;смещение процедуры в DX MOV AX,SEG ROUTINE ;сегмент процедуры в DS MOV DS,AX ; MOV AL,70H ;номер вектора прерывания MOV AH,25H ;функция установки вектора INT 21H ;установка вектора ;---завершение программы MOV DX,FINISH+100H ;вычисляем смещение конца MOV BYTE PTR ES:1,27H ;посылаем 27H в PSP RET ;завершаем процедуру Функция 31H прерывания 21H работает аналогично, за исключением того, что в DX должно содержаться число 16-байтных параграфов, требуемых процедуре (вычисление размера процедуры, начиная от начала PSP - см. в примере [1.3.1]). Преимуществом этой функции является то, что она передает родительской программе код выхода, дающий информацию о статусе процедуры. Родительская программа получает этот код с помощью функции 4DH прерывания 21H. Коды выхода обсуждаются в [7.2.5]. 1.3.5 Загрузка и запуск программных оверлеев. Оверлеи - это части программы, которые остаются на диске, в то время как тело программы резидентно в памяти. Когда требуется функция, выполняемая каким-либо оверлеем, то он загружается в память и программа вызывает его как процедуру. Различные оверлеи могут загружаться в одно и то же место памяти, перекрывая преды- дущий код. Например, программа ведения базы данных может загру- зить процедуру сортировки, а затем перекрыть ее процедурой гене- рации отчетов. Эта техника используется для экономии памяти. Но она хороша только для тех процедур, которые не используются пос- тоянно, иначе частые обращения к диску приведут к тому, что прог- рамма будет выполняться слишком медленно. Средний уровень. MS DOS использует функцию EXEC для загрузки оверлеев. Эта функция, номер 4BH прерывания 21H, используется также для загруз- ки и запуска одной программы из другой, если поместить код 0 в AL [1.3.2]. Если в AL поместить код 3, то тогда будет загружен оверлей. В этом случае не создается PSP, поэтому оверлей не уста- навливается как независимая программа. Такая процедура просто загружает оверлей, не передавая ему управления. Имеется два способа обеспечить память для оверлея. Может быть использована либо область внутри тела программы, либо специально отведена область памяти за пределами головной программы. Функции EXEC передается только сегментный адрес, в качестве позиции, куда будет загружен оверлей. Когда оверлей загружается в тело головной программы, то программа должна вычислить номер параграфа, куда будет загружаться оверлей, сама. С другой стороны, при загрузке в специально отведенную память MS DOS обеспечивает программу номе- ром параграфа. В нижеприведенном примере используется загрузка в отведенную память. Поскольку DOS отводит программе всю доступную память, то сначала необходимо освободить память с помощью функции 4AH. Функ- ция 48H отводит блок памяти достаточно большой, чтобы он мог принять самый большой из оверлеев. Эта функция возвращает значе- ние сегмента блока в AX, и этот номер параграфа определяет куда будет загружен оверлей, а также по какому адресу оверлей будет вызываться головной программой. Эти функции детально обсуждаются в [1.3.1]. Кроме кода 3, засылаемого в AL, Вы должны установить для этой функции еще два параметра. DS:DX должны указывать на строку, даю- щую путь к файлу оверлея, завершаемую байтом ASCII 0. Необходимо указывать полное имя файла, включая расширение .COM или .EXE, поскольку DOS в данном случае не считает, что он ищет программный файл. Наконец, ES:BX должны указывать на 4-байтный блок параметров, который содержит (1) 2-байтный номер параграфа, куда будет загру- жаться оверлей и (2) 2-байтный фактор привязки, который будет использоваться для привязки адресов в оверлее (привязка объяс- няется в [1.3.6]). В качестве номера параграфа надо использовать число, возвращаемое в AX, для номера параграфа отведенного блока памяти. Фактор привязки дает смещение, по которому могут быть вычислены адреса требующих привязки параметров в оверлее. Исполь- зуйте номер параграфа, куда загружается оверлей. После того как он установлен, вызовите функцию и оверлей будет загружен. Просто изменяя путь к оверлейному файлу, можно вновь и вновь вызывать эту функцию, загружая все новые и новые оверлеи. Если при возвра- те установлен флаг переноса, то была ошибка и ее код будет возв- ращен в AX. Код равен 1, если указан неверный номер функции, 2 - если файл не найден, 5 - при дисковых ошибках и 8 - при отсутст- вии достаточной памяти. После того как оверлей загружен в память, к нему можно полу- чить доступ как к далекой (far) процедуре. В сегменте данных должен быть установлен двухсловный указатель, определяющий этот вызов. Сегментная часть указателя просто равна текущему кодовому сегменту. Смещение оверлея должно быть вычислено нахождением разницы между сегментами кода и оверлея и умножением результата на 16 (переводя величину из параграфов в байты). В нижеприведен- ном примере две переменные OVERLAY_OFFSET и CODE_SEG помещены одна за другой для правильной установки указателя. Однажды загру- женный, оверелей затем можем вызываться инструкцией CALL DWORD PTR OVERLAY_OFFSET. Оверлей может быть полной программой со своими сегментами данных и стека, хотя как правило используется стековый сегмент вызывающей программы. При вызове оверлея значение сегмента его собственного сегмента данных должно быть помещено в DS. ;---завершаем программу фиктивным сегментом (см. [1.3.1]): ZSEG SEGMENT ZSEG ENDS ;---в сегменте данных OVERLAY_SEG DW ? OVERLAY_OFFSET DW ? ;смещение оверлея CODE_SEG DW ? ;сегмент оверлея - должен PATH DB 'A:OVERLAY.EXE' ;следовать за смещением 0BLOCK DD 0 ;4-байтный блок параметров ;---освобождаем память MOV CODE_SEG,CS ;создаем копию CS MOV AX,ES ;копируем значение сегмента PSP MOV BX,ZSEG ;адрес сегмента конца программы SUB BX,AX ;вычисляем разность MOV AH,4AH ;номер функции SETBLOCK INT 21H ;освобождаем память JC SETBLK_ERR ;флаг переноса говорит об ошибке ;---отводим память для оверлея MOV BX,100H ;отводим для оверлея 1000H байт MOV AH,48H ;функция отведения памяти INT 21H ;теперь AX:0 указывает на блок JC ALLOCATION_ERR ;флаг переноса говорит об ошибке MOV OVERLAY_SEG,AX ;запасаем адрес сегмента оверлея ;---вычисление смещения оверлея в кодовом сегменте MOV AX,CODE_SEG ;вычитаем значение сегмента оверлея MOV BX,OVERLAY_SEG ;из значения сегмента кода SUB BX,AX ;BX содержит число параграфов MOV CL,4 ;сдвигаем это число на 4 бита влево SHL BX,CL ;чтобы получить величину в байтах MOV OVERLAY_OFFSET,BX ;запоминаем смещение ;---загрузка первого оверлея MOV AX,SEG BLOCK ;ES:BX указывает на блок параметров MOV ES,AX ; MOV BX,OFFSET BLOCK ; MOV AX,OVERLAY_SEG ;помещаем адрес сегмента оверлея в MOV [BX],AX ;первое слово блока параметров MOV [BX]+2,AX ;сегмент оверлея - фактор привязки LEA DX,PATH ;DS:DX указывает на путь к файлу MOV AH,48H ;номер функции EXEC MOV AL,3 ;код загрузки оверлея INT 21H ;загружаем оверлей JC LOAD_ERROR ;флаг переноса говорит об ошибке ;---теперь программа занимается своими делами . . CALL DWORD PTR OVERLAY_OFFSET ;вызов оверлея . ;нужно указывать DWORD PTR, так как оверлей - . ;далекая процедура ;---посмотрите эту структуру, когда будете писать оверлей DSEG SEGMENT ;как обычно, устанавливаем сегмент данных . ;опускаем стековый сегмент (используется . ;стек вызывающей программы) DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' OVERLAY PROC FAR ;всегда "далекая" процедура ASSUME CS:CSEG,DS:DSEG PUSH DS ;храним DS вызывающей программы MOV AX,DSEG;устанавливаем DS оверлея MOV DS,AX . . POP DS ;восстанавливаем DS при завершении RET OVERLAY ENDP CSEG ENDS END 1.3.6 Преобразование программ из типа .EXE в тип .COM. Программисты на ассемблере имеют возможность преобразовать свои программы из обычного формата EXE в формат COM. Файлы EXE имеют заголовок, содержащий информацию для привязки; DOS привязы- вает некоторые адреса программы при загрузке. С другой стороны, файлы COM существуют в таком виде, что привязка не требуется - они хранятся уже в том виде, в котором загружаемая программа должна быть в памяти машины. По этой причине файлы EXE по меньшей мере на 768 байтов больше на диске, чем их COM эквиваленты (хотя при загрузке в память они будут занимать одинаковое место). Файлы COM также быстрее загружаются, поскольку не требуется привязки. Других преимуществ у них нет, а некоторые программы слишком слож- ны и слишком велики, чтобы их можно было преобразовать в тип COM. Привязка - это процесс установки адресов, связанных с сегмент- ным регистром. Например, программа может указывать на начало области данных следующим кодом: MOV DX,OFFSET DATA_AREA MOV AX,SEG DATA_AREA MOV DS,AX Смещение в DX связано с установкой сегментного регистра DS. Но какое значение должен принимать сам DS? Программа требует абсо- лютный адрес, но номер параграфа, в котором будет располагаться DATA_AREA зависит от того, в какое место в памяти будет загружена программа - а это зависит от версии MS DOS, а также от того, какие резидентные программы будут находиться в младших адресах памяти. По этой причине во время компоновки программы можно толь- ко установить некоторые сегментные значения через смещения отно- сительно начала программы. Затем, когда DOS осуществляет привяз- ку, значение начального адреса программы прибавляется к сегмент- ным значениям, давая абсолютные адреса, требуемые в сегментном регистре. На рис. 1-6 показан процесс привязки. Файлы COM не нуждаются в привязке, поскольку они хранятся в таком виде, что не нуждаются в фиксации сегмента. Все в программе хранится относительно начала кодового сегмента, включая все дан- ные и стек. По этой причине вся программа не может превышать 65535 байт по длине, что соответствует максимальному смещению, которое существует в используемой схеме адресации (поскольку верхняя часть этого блока занята стеком, то реальное пространство доступное для кода и данных немного меньше чем 65535 байт, хотя стековый сегмент при необходимости может быть вынесен за границу 64K байтного блока). В файлах COM все сегментные регистры указы- вают на начало PSP; сравните с файлами EXE, где DS и ES инициали- зируются аналогичным образом, но CS указывает на первый байт следующий за PSP. Для представления программы в виде файла COM требуется соблю- дение следующих правил: 1. Не оформляйте программу в виде процедуры. Вместо этого, поместите в самое начало метку, вроде START, и завершите програм- му оператором END START. 2. Поместите в начале программы оператор ORG 100H. Этот опера- тор указывает начало кода (т.е. устанавливает счетчик комманд). Программы COM начинаются с 100H, что является первым байтом, следующим за PSP, поскольку CS указывает на начало PSP, которое расположено на 100H байт ниже. Для того чтобы начать выполнение с любого другого места поместите по адресу 100H инструкцию JMP. 3. Оператор ASSUME должен устанавливать DS, ES и SS таким образом, чтобы они совпадали со значением для кодового сегмента, например, ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG. 4. Данные программы могут помещаться в любом месте программы, до тех пор, пока они не перемешаны с кодом. Лучше начинать прог- раммы с области данных, поскольку макроассемблер может выдавать сообщения об ошибках при первом проходе, если имеются ссылки на идентификатор данных, который еще не обнаружен. Для перехода к началу кода используйте в качестве первой команды программы инст- рукцию JMP. 5. Нельзя использовать фиксацию сегментов типа MOV AX,SEG NEW_DATA. Достаточно указания одного смещения метки. В частности, нужно опускать обычный код, используемый в начале программы для установки сегмента данных, MOV AX,DSEG / MOV DS,AX. 6. Стековый сегмент полностью опускается в начальном коде. Указатель стека инициализируется на вершину адресного пространст- ва 64K, используемого программой (напоминаем, что стек растет вниз в памяти). В программах COM он должен быть сделан меньше чем 64K, SS и SP могут быть изменены. Имейте ввиду, что при компонов- ке программы компоновщик выдаст сообщение об ошибке, указывающее, что сегмент стека отсутствует. Игнорируйте его. 7. Завершите программу либо инструкцией RET, либо прерыванием 20H. Прерывание 20H - это стандартная функция для завершения программы и возврата управления в DOS. Даже когда программа за- вершается инструкцией RET, на самом деле используется прерывание 20H. Это происходит потому, что вершина стека первоначально со- держит 0. При выполнении завершающей инструкции программы RET, 0 выталкивается из стека, переназначая счетчик команд на начало PSP. Находящаяся в этой ячейке функция 20H, выполняется как сле- дующая инструкция программы, вызывая передачу управления в DOS. Все это означает, что Вам не надо при старте программы помещать на стек DS и 0 (PUSH DS / MOV AX,0 / PUSH AX), как это требуется для EXE файлов. После того как программа сконструирована таким образом, ас- семблируйте и компонуйте ее как обычно. Затем преобразуйте ее в форму COM c помощью утилиты EXE2BIN, имеющейся в MS DOS. Если имя программы, построенной компоновщиком MYPROG.EXE, то просто введи- те команду EXE2BIN MYPROG. В результате Вы получите программный файл с именем MYPROG.BIN. Все что Вам останется после этого сде- лать - переименовать этот файл в MYPROG.COM. Вы можете также сразу использовать команду EXE2BIN MYPROG MYPROG.COM, для получе- ния файла с расширением COM. Низкий уровень. В данном примере содержится полная короткая программа, которая по установке переключателей определяет количество накопителей в машине и затем выводит сообщение на экран. Она может служить примером короткой утилиты того сорта, для которых формат COM идеален. CSEG SEGMENT ORG 100H ASSUME CS:CSEG, DS:CSEG, SS:CSEG ;---данные START: JMP SHORT BEGIN ;переход к коду MESSAGE1 DB 'The dip switches are set for $' MESSAGE2 DB 'disk drive(s).$' ;---печать первой части сообщения BEGIN: MOV AH,9 ;функция 9 прерывания 21H - вывод MOV DX,OFFSET MESSAGE1 ;строки INT 21H ;выводим строку PUSH AX ;сохраняем номер функции на будущее ;---получаем установку переключателей из порта A микросхемы 8255 IN AL,61H ;получаем байт из порта B OR AL,10000000B ;устанавливаем бит 7 OUT 61H,AL ;заменяем байт IN AL,60H ;получаем установку переключат. AND AL,11000000B ;выделяем старшие 2 бита MOV CL,6 ;подготовка к сдвигу AL вправо SHR AL,CL ;сдвигаем 2 бита в начало ADD AL,49 ;добавляем 1, чтобы считать с 1 ;и 48 для перевода в ASCII MOV DL,AL ;помещаем результат в DL MOV AL,61H ;должны восстановить порт B AND AL,01111111B ;сбрасываем бит 7 OUT 61H,AL ;возвращаем байт ;---печать числа накопителей MOV AH,2 ;функция 2 прерывания 21H INT 21H ;печатаем число из DL ;---печать второй половины сообщения POP AX ;берем номер функции со стека MOV DX,OFFSET MESSAGE2 INT 21H ;выводим строку INT 20H ;завершение программы CSEG ENDS END START Глава 2. Таймеры и звук. Раздел 1. Установка и чтение таймера. Все IBM PC используют микросхему таймера 8253 (или 8254) для согласования импульсов от микросхемы системных часов. Число цик- лов системных часов преобразуется в один импульс, а последова- тельность этих импульсов подсчитывается для определения времени, или они могут быть посланы на громкоговоритель компьютера для генерации звука определенной частоты. Микросхема 8253 имеет три идентичных независимых канала, каждый из которых может программи- роваться. Микросхема 8253 работает независимо от процессора. Процессор программирует микросхему и затем обращается к другим делам. Таким образом 8253 действует как часы реального времени - она считает свои импульсы независимо от того, что происходит в компьютере. Однако, максимальный программируемый интервал составляет прибли- зительно 1/12 секунды. Для подсчета интервалов времени в часы и минуты нужны какие-то другие средства. Именно по этой причине импульсы от нулевого канала микросхемы таймера накапливаются в переменной, находящейся в области данных BIOS. Этот процесс пока- зан на рис. 2-1. Это накопление обычно называется подсчетом вре- мени суток. 18.2 раза в секунду выход канала 0 обрабатывается аппаратным прерыванием (прерыванием таймера), которое ненадолго останавливает процессор и увеличивает счетчик времени суток. Число 0 соответствует полночи 12:00; когда счетчик достигает значения эквивалентного 24 часам, он сбрасывается на ноль. Другое время в течение суток легко определяется делением показателя счетчика на 18.2 для каждой секунды. Счетчик времени суток ис- пользуется в большинстве операций, связанных со временем. 2.1.1 Программирование микросхемы таймера 8253/8254. Каждый из трех каналов микросхемы таймера 8253 (8254 для AT) состоит из трех регистров. Доступ к каждой группе из трех регист- ров осуществляется через один порт; номера портов от 40H до 42H соответствуют каналам 0 - 2. Порт связан с 8-битным регистром ввода/вывода, который посылает и принимает данные для этого кана- ла. Когда канал запрограммирован, то через этот порт посылается двухбайтное значение, младший байт сначала. Это число передается в 16-битный регистр задвижки (latch register), который хранит это число и из которого копия помещается в 16-битный регистр счетчи- ка. В регистре счетчика число уменьшается на единицу каждый раз, когда импульс от системных часов пропускается через канал. Когда значение этого числа достигает нуля, то канал выдает выходной сигнал и затем новая копия содержимого регистра задвижки передви- гается в регистр счетчика, после чего процесс повторяется. Чем меньше число в регистре счетчика, тем быстрее ритм. Все три кана- ла всегда активны: процессор не включает и не выключает их. Теку- щее значение любого из регистров счетчика может быть прочитано в любой момент времени, не влияя на счет. Каждый канал имеет две входные и одну выходную линии. Выходная линия выводит импульсы, возникающие в результате подсчета. Назна- чение этих сигналов варьируется в зависимости от типа IBM PC: Канал 0 используется системными часами времени суток. Он уста- навливается BIOS при старте таким образом, что выдает импульсы приблизительно 18.2 раза в секунду. 4-байтный счетчик этих им- пульсов хранится в памяти по адресу 0040:006C (младший байт хра- нится первым). Каждый импульс инициирует прерывание таймера (но- мер 8) и именно это прерывание увеличивает показание счетчика. Это аппаратное прерывание, поэтому оно обрабатывается всегда, независимо от того, чем занят процессор, если только разрешены аппаратные прерывания (см. обсуждение в [1.2.2]). Выходная линия используется также для синхронизации некоторых дисковых операций, поэтому если Вы изменили ее значение, то Вам необходимо восстано- вить первоначальное значение перед обращением к диску. Канал 1 управляет обновлением памяти на всех машинах кроме PCjr, поэтому его лучше не трогать. Выходная линия этого канала связана с микросхемой прямого доступа к памяти [5.4.2] и ее им- пульс заставляет микросхему DMA обновить всю память. На PCjr канал 1 служит для преобразования входных данных с клавиатуры из последовательной в параллельную форму. PCjr не использует микрос- хему прямого доступа к памяти, поэтому когда он вместо этого прогоняет данные через процессор, то прерывание от таймера забло- кировано. Канал 1 используется для подсчета заблокированных им- пульсов часов времени суток, с тем чтобы можно было обновить значение счетчика после завершения дисковых операций. Канал 2 связан с громкоговорителем компьютера и он производит простые прямоугольные импульсы для генерации звука. Программисты имеют больший контроль над вторым каналом, чем над остальными. Простые звуки могут генерироваться одновременно с другими прог- раммными операциями, а более сложные звуковые эффекты могут быть достигнуты за счет использования процессора. Канал 2 может быть отсоединен от громкоговорителя и использоваться для синхрониза- ции. Наконец, выходная линия канала 2 связана с динамиком компью- тера. Однако динамик не будет генерировать звук до тех пор пока не сделаны определенные установки микросхемы интерфейса с перифе- рией 8255. Две входные линии для каждого канала состоят из линии часов, которая передает сигнал от микросхемы системных часов и линии, называемой воротами (gate), которая включает и выключает сигнал от часов. Ворота всегда открыты для сигналов часов по каналам 0 и 1. Но они могут быть закрытыми для канала 2, что позволяет неко- торые специальные манипуляции со звуком. Ворота закрываются уста- новкой младшего бита порта с адресом 61H, который является ре- гистром микросхемы 8255; сброс этого бита снова открывает ворота. Эта микросхема обсуждается в [1.1.1]. Отметим что - как и выход канала 2 - бит 1 порта 61H связан с динамиком и также может ис- поьзоваться для генерации звука. На рис. 2-2 приведена диаграмма микросхемы таймера 8253. Микросхема таймера может использоваться непосредственно для временных операций, но это редко бывает удобным. Ввод с часов производится 1.19318 миллионов раз в секунду (даже на AT, где системные часы идут быстрее, микросхема таймера получает сигнал с частотой 1.19 Мгц). Поскольку максимальное число, которое может храниться в 16 битах, равно 65535 и поскольку это число делится на частоту импульсов от часов, равную 18.2, то максимальный воз- можный интервал между импульсами равен приблизительно 1/12 секун- ды. Поэтому большинство временных операций используют счетчик времени суток BIOS. Для подсчета времени читается значение време- ни суток и сравнивается с некоторым ранее запомненным значением для определения числа импульсов, прошедших с того момента. Спе- циальный способ, описанный в [2.1.7], позволяет испоьзовать счет- чик времени суток для операций в реальном времени. 8253 предоставляет разработчикам оборудования 6 режимов работы для каждого канала. Программисты обычно ограничиваются третьим режимом, как для канала 0 при синхронизации, так и для канала 2 для синхронизации или генерации звука. В этом режиме, как только регистр задвижки получает число, он немедленно загружает копию в регистр счетчика. Когда значение в счетчике достигает нуля ре- гистр задвижки мгновенно перезагружает счетчик и т.д. В течение половины отсчета выходная линия включена, а в течение половины - выключена. В результате получаются прямоугольные волны, которые одинаково пригодны как для генерации звука, так и для подсчета. 8-битный командный регистр управляет способом загрузки чисел в канал. Адрес порта для этого регистра равен 43H. Командному ре- гистру передается байт, который говорит какой канал программиро- вать, в каком режиме, а также один или оба байта регистра задвиж- ки должны быть переданы. Он показывает также будет ли число в двоичной или BCD (двоичнокодированной десятичной) форме. Значение битов этого регистра таково: бит 0 если 0, двоичные данные, иначе BCD 3-1 номер режима, 1 - 5 (000 - 101) 5-4 тип операции: 00 = передать значение счетчика в задвижку 01 = читать/писать только старший байт 10 = читать/писать только младший байт 11 = читать/писать старший байт, потом младший 7-6 номер программируемого канала, 0 - 2 (00 -10) Короче говоря, для программирования микросхемы 8253 надо вы- полнить три основных шага. После того как третий шаг завершен, запрограммированный канал немедленно начинает функционировать по новой программе. 1. Послать в командный регистр (43H) байт, представляющий цепочку битов, которые выбирают канал, статус чтения/записи, режим операции и форму представления чисел. 2. Для канала 2 надо разрешить сигнал от часов, установив в 1 бит 0 порта с адресом 61H. (Когда бит 1 этого регистра установлен в 1, то канал 2 управляет динамиком. Сбросьте его в 0 для опера- ций синхронизации.) 3. Вычислите значение счетчика от 0 до 65535, поместите его в AX, и пошлите сначала младший, а затем старший байт в регистр ввода/вывода канала (40H - 42H). Каналы микросхемы 8253 работают всегда. По этой причине прог- раммы всегда должны восстанавливать начальные установки регистров 8253 перед завершением. В частности, если при завершении програм- мы генерируется звук, то он будет продолжаться даже после того, как MS DOS получит управление и загрузит другую программу. Имейте это ввиду при написании процедуры выхода по Ctrl-Break [3.2.8]. Низкий уровень. В данном примере канал 0 программируется на другое значение, чем установлено BIOS при старте. Причина изменения установки состоит в том, чтобы изменить интервал изменения счетчика времени суток на большую величину, чем 18.2 раза в секунду. Частота об- новления счетчика изменяется, скажем, на 1000 раз в секунду, с целью проведения точных лабораторных измерений. Значение задвижки должно быть 1193 (1193180 тактов в секунду / 10000). Как читать текущее значение регистра счетчика см. в примере [2.1.8]. Перед дисковыми операциями оригинальное значение задвижки должно быть восстановлено, поскольку канал 0 используется для синхронизации дисковых операций. Максимально возможное значение - 65535 тактов часов между импульсами от канала - может быть достигнуто засылкой 0 в регистр задвижки (0 немедленно превращается в 65535 при уменьшении на единицу. ;---установка регистров ввода/вывода COMMAND_REG EQU 43H ;адрес командного регистра CHANNEL_0 EQU 40H ;адрес канала 0 MOV AL,00110110B ;установка битов для канала 2 OUT COMMAND_REG,AL ;засылка в командный регистр ;---посылка счетчика в задвижку MOV AX,1193 ;счетчик для 100 импульсов/сек. OUT CHANNEL_2,AL ;посылка младшего байта MOV AL,AH ;готовим для посылки старший байт OUT CHANNEL_2,AL ;посылка старшего байта 2.1.2 Установка/чтение времени. При старте MS DOS запрашивает у пользователя текущее время. Введенное значение помещается в 4 байта, хранящие счетчик времени суток (начиная с 0040:006C, младший байт хранится первым). Но сначала оно преобразуется в форму, в которой подсчитывается время суток, т.е. время преобразуется в число восемнадцатых долей се- кунды, прошедших с полночи. Это число постоянно обновляется 18.2 раз в секунду прерыванием таймера. Когда появляется очередной запрос на время, то текущее значение счетчика времени суток преобразуется обратно в привычный формат часы-минуты-секунды. Если при старте не было введено значения, то счетчик устанавли- вается в ноль, как будто сейчас полночь. Компьютеры снабженные микросхемой календаря-часов могут автоматически устанавливать счетчик времени суток. Высокий уровень. TIME$ устанавливает или получает время в виде строки чч:мм:сс, где часы меняются от 0 до 23, начиная с полуночи. Для 5:10 дня: 100 TIME$ = "17:10:00" 'установка времени 110 PRINT TIME$ 'вывод времени Поскольку TIME$ возвращает строку, то для выделения отдельных частей показания часов можно использовать строковые функции MID$, LEFT$ и RIGHT$. Например, чтобы преобразовать время 17:10:00 в 5:10 Вы должны вырезать строку символов, соответствующую часам, преобразовать ее в числовой вид (используя функцию VAL), вычесть 12, а затем представить результат опять в виде строки: 100 T$ = TIME$ 'получаем строку времени 110 HOUR$ = LEFT$(T$,2) 'выделяем значение часов 120 MINUTES$ = MID$(T$,4,2) 'выделяем значение минут 130 NEWHOUR = VAL(HOUR$) 'преобразуем часы в число 140 IF NEWHOUR > 12 THEN NEWHOUR = NEWHOUR - 12 150 NEWHOUR$ = STR$(NEWHOUR) 'новое значение в строку 160 NEWTIME$ = NEWHOUR$ + ":" + MINUTES$ 'делаем новую строку Средний уровень. MS DOS предоставляет прерывания для чтения и установки време- ни, производя необходимые преобразования между значением счетчика времени суток и часами-минутами-секундами. Время выдается с точ- ностью до 1/100 секунды, но поскольку счетчик времени суток об- новляется с частотой в пять раз меньшей, то показания сотых се- кунд очень приближенные. Функция 2CH прерывания 21H выдает время, а функция 2DH - устанавливает его. В обоих случаях CH содержит часы (от 0 до 23, где 0 соответствует полночи), CL - минуты (от 0 до 59), DH - секунды (от 0 до 59) и DL - сотые доли секунд (от 0 до 99). Кроме того при получении времени функцией 2CH, AL содержит номер дня недели (0 = воскресенье). Значение дня будет верным только если была установлена дата. DOS вычисляет номер дня недели по дате. Отметим также, что при установке времени функцией 2DH, AL отмечает правильность введенного значения времени (0 = пра- вильно, FF = неправильно). ;---установка времени MOV CH,HOURS ;вводим значения времени MOV CL,MINUTES ; MOV DH,SECONDS ; MOV DL,HUNDREDTHS ; MOV AH,2DH ;номер функции установки времени INT 21H ;устанавливаем время CMP AH,0FFH ;проверяем правильность значения JE ERROR ;переход на обработку ошибки ;---получение времени MOV AH,2CH ;номер функции получения времени INT 21H ;получаем время MOV DAY_OF_WEEK,AH ;получаем день недели из AH Низкий уровень. Если Вы изменили скорость импульсов канала 1 микросхемы 8253 для специальных приложений, то Вам необходимо написать свою про- цедуру декодирования показаний счетчика времени суток. BIOS поз- воляет диапазон значений счетчика от 0 до 1.573 миллиона и это может быть изменено только путем изменения прерывания таймера. Поэтому часы, реально показывающие сотые доли секунды, не могут работать 24 часа без специально написанной программы. Отметим также, что байт 0040:0070 устанавливается в ноль при старте, а затем увеличивается на 1 (не больше) по ходу часов. 2.1.3 Установка/чтение даты. При включении компьютера MS DOS запрашивает у пользователя текущие дату и время. Время записывается в области данных BIOS. Дата же содержится в переменной в COMMAND.COM. Она хранится в формате трех последовательных байтов, которые содержат соответст- венно день месяца, номер месяца и номер года, начиная с 0, где 0 соответствует 1980 году. В отличии от счетчика времени суток, адрес даты в памяти меняется с изменением версии DOS и положением в памяти COMMAND.COM. По этой причине для получения даты всегда надо использовать готовые утилиты Бейсика или MS DOS, а не обра- щаться к этой переменной напрямую. Машины, оборудованные микросхемой календаря-часов, автомати- чески устанавливают время и дату с помощью специальной программы (обычно запускаемой при старте через файл AUTOEXEC.BAT). Как получить доступ к микросхеме календаря-часов, см. [2.1.4]. Отме- тим также, что когда счетчик времени суток BIOS переходит через отметку 24 часов, MS DOS меняет дату. Высокий уровень. Оператор Бейсика DATE$ устанавливает или получает дату в виде строки формата ММ-ДД-ГГГГ. Можно использовать косую черту (/) вместо дефиса (-). Первые две цифры года могут быть опущены. Для 31-го октября 1984 г.: 100 DATE$ = "10/31/84" 'установка даты 110 PRINT DATE$ 'вывод даты ... и на дисплее будет выведено: 10-31-1984. Средний уровень. Функции 2AH и 2BH прерывания 21H получают и устанавливают дату. Для получения даты поместите в AH 2AH и выполните прерыва- ние. При возврате CX будет содержать год в виде числа от 0 до 119, что соответствует диапазону лет 1980 - 2099 (можно сказать что выдается смещение относительно 1980 г.). DH содержит номер месяца, а DL - день. MOV AH,2AH ;номер функции получения даты INT 21H ;получение даты MOV DAY,DL ;день из DL MOV MONTH,DH ;месяц из DH ADD CX,1980 ;добавляем базу к году MOV YEAR,CX ;получаем номер года Для установки даты поместите день, месяц и год в те же регист- ры и выполните функцию 2BH. Если значения, указанные для даты неверны, то в AL будет возвращено FF, в противном случае - 0. MOV DL,DAY ;помещаем день в DL MOV DH,MONTH ;помещаем месяц в DH MOV CX,YEAR ;помещаем год в CX SUB CX,1980 ;берем смещение относительно 1980 MOV AH,2BH ;номер функции установки даты INT 21H ;установка даты CMP AH,0FFH ;проверяем успешность операции JE ERROR ;неверная дата, идем на обработку ошибки 2.1.4 Установка/чтение часов реального времени. Часы реального времени имеют свой собственный процессор, кото- рый может подсчитывать время не влияя на другие компьютерные опе- рации. Они имеют также независимый источник питания, используемый когда компьютер выключен. Программно можно как читать, так и устанавливать часы рельного времени. Обычно имеется дополнитель- ное программное обеспечение, которое устанавливает счетчик време- ни суток BIOS и переменную даты DOS таким образом, чтобы они соответствовали текущим показаниям часов реального времени. Но можно программно проверить соответствие между ними и при обнару- жении разногласий принять необходимые меры. Различные установки времени и даты осуществляются через набор адресов портов. Многие многофункциональные платы расширения для IBM PC имеют часы реального времени, но, к сожалению, нет стан- дартной микросхемы и диапазона адресов портов. AT оборудуется часами реального времени, основанными на микросхеме MC146818 фирмы Motorola, которые используют те же регистры, что и микрос- хема, содержащая данные о конфигурации системы. Доступ к этим регистрам можно получить, послав сначала номер требуемого регист- ра в порт 70H, а затем прочитав значение регистра через порт 71H. Регистры, связанные с часами, следующие: Номер регистра Функция 00H Секунды 01H Секундная тревога 02H Минуты 03H Минутная тревога 04H Часы 05H Часовая тревога 06H День недели 07H День