месяца 08H Месяц 09H Год 0AH регистр статуса A 0BH регистр статуса B 0CH регистр статуса C 0DH регистр статуса D Биты четырех статусных регистров выполняют различные функции, из которых интерес для программистов могут представлять следую- щие: Регистр A: бит 7 1 = идет модификация времени (надо ждать значения 0, чтобы читать) Регистр B: бит 6 1 = разрешено периодическое прерывание бит 5 1 = разрешено прерывание тревоги бит 4 1 = разрешено прерывание конца модификации бит 1 1 = часы считаются до 24, 0 = до 12 бит 0 1 = разрешено запоминание времени суток Часы реального времени на AT могут вызывать аппаратное преры- вание IRQ8. Программа может установить вектор этого прерывания на любую процедуру, которую требуется выполнить в определенное время [1.2.3]. Используйте вектор 4AH. Операции в реальном времени, производимые таким образом, менее хлопотны, чем обсуждаемые в [2.1.7] (хотя и ценой компактности программ). Прерывание может вызываться одним из трех способов, каждый из которых запрещен при старте. Периодическое прерывание происходит через определенные интервалы времени. Периодичность приближенно равна одной милли- секунде. Прерывание тревоги происходит когда значение трех ре- гистров тревоги совпадает со значениями соответствующих временных регистров. Прерывание конца модификации происходит после каждого обновления значений регистров микросхемы. Прерывание 1AH расширено в BIOS AT, чтобы оно позволяло читать и устанавливать часы реального времени. Поскольку показания ни- когда не состоят более чем их двух десятичных цифр, то значения времени выдаются в двоично-кодированной десятичной форме (BCD), когда байт делится на две половины и каждая десятичная цифра представляется четырьмя битами. Такой формат позволяет легко переводить числа в форму ASCII. Программе нужно только сдвинуть половину байта в младший конец регистра и добавить 48 для получе- ния кода ASCII, соответствующего данному числу. Для всех IBM PC функции 0 и 1 прерывания 1AH читают и устанавливают счетчик вре- мени суток BIOS. Для часов реального времени AT имеется шесть новых функций: Функция 2: Чтение времени из часов реального времени При возврате: CH = часы в BCD CL = минуты в BCD DH = секунды в BCD Функция 3: Установка времени часов реального времени При входе: CH = часы в BCD CL = минуты в BCD DH = секунды в BCD DL = if daylight savings, else 1 Функция 4: Чтение даты из часов реального времени При возврате: CH = век в BCD (19 или 20) CL = год в BCD (с 1980) DH = месяц в BCD DL = день месяца в BCD Функция 5: Установка даты часов реального времени При входе: CH = век в BCD (19 или 20) CL = год в BCD (с 1980) DH = месяц в BCD DL = день месяца в BCD Функция 6: Установка тревоги для часов реального времени При входе: CH = часы в BCD CL = минуты в BCD DH = секунды в BCD Функция 7: Сброс тревоги (нет входных регистров) Тревога устанавливается как смещение, относительно текущего мо- мента времени. Максимальный период равен 23:59:59. Как уже гово- рилось выше, вектор прерывания 4AH должен указывать на процедуру обработки тревоги. Отметим, что если часы не работают (наиболее вероятно, из-за отсутствия питания), то выполнение функций 2, 4 и 6 устанавливает флаг переноса. 2.1.5 Задержка программных операций. Если Вы осуществляете задержку в программе посредством пустого цикла, то Вам может потребоваться много времени для того, чтобы добиться нужного времени задержки. Даже если Вы определите тре- буемую длительность, то нельзя быть уверенным, что Ваша программа будет давать нужное время задержки при всех условиях. Длитель- ность цикла может меняться в зависимости от используемого компи- лятора (или, для Бейсика, от того, компилируется программа или нет). А в наше время, когда имеется большой набор машин совмести- мых с IBM PC - имеющих широкий диапазон скорости процессора - даже цикл на языке ассемблера может приводить к различным време- нам задержки. Поэтому разумно определять время программной за- держки непосредственно по часам. Частота отсчета 18.2 раза в секунду, используемая для модификации счетчика времени суток, должна вполне удовлетворять большинство потребностей (как увели- чить частоту отсчетов см. [2.1.1]). Чтобы обеспечить задержку данной продолжительности, программа должна подсчитать требуемое число импульсов счетчика времени суток. Это значение добавляется к считанному текущему значению счетчика. Затем программа постоянно считывает значение счетчика и сравнивает его с запомненным. Когда достигается равенство, то требуемая задержка прошла и можно продолжать выполнение програм- мы. Четыре байта, в которых хранится значение счетчика времени суток хранятся, начиная с адреса 0040:006C (как обычно, начиная с младшего байта). Для задержек меньших 14 секунд можно пользовать- ся только младшим байтом. Два младших байта позволяют задержки до одного часа (точнее, на пол-секунды меньше, чем час). Высокий уровень. В Бейсике можно использовать оператор SOUND [2.2.2] со значе- нием частоты, равным 32767. В этом случае звук не будет генериро- ваться вообще. Это отсутствие звука будет длиться столько отсче- тов времени суток, сколько Вы укажете. Для 5-секундной задержки нужен 91 отсчет (5 * 18.2). Поэтому 100 SOUND 32767,91 'останавливает программу на 5 секунд Для прямого чтения счетчика времени суток нужно: 100 DEF SEG = 0 'установка сегмента на начало памяти 110 LOWBYTE = PEEK(&H46C) 'получение младшего байта 120 NEXTBYTE = PEEK(&H46D) 'получение следующего байта 130 LOWCOUNT = NEXTBYTE*256 + LOWBYTE 'значение двух байтов Средний уровень. Прочитайте значение счетчика времени суток BIOS, используя функцию 0 прерывания 1AH и добавьте к нему необходимое число импульсов по 1/18 секунды. После этого считывайте текущие значе- ния счетчика времени суток, постоянно сравнивая с требуемой вели- чиной. При достижении равенства надо кончать задержку. Прерывание 1AH возвращает два младших байта в DX (большинство задержек укла- дываются в этих пределах), поэтому два старших байта, возвращае- мые в CX, могут игнорироваться, что позволит Вам избежать 32-байтных операций. В данном примере установлена задержка на 5 секунд, что соответствует 91 отсчету. ;---получение значения счетчика и установка задержки MOV AH,0 ;номер функции для "чтения" INT 1AH ;получаем значение счетчика ADD DX,91 ;добавляем 5 сек. к младшему слову MOV BX,DX ;запоминаем требуемое значение в BX ;---постоянная проверка значения счетчика времени суток BIOS REPEAT: INT 1AH ;получаем значение счетчика CMP DX,BX ;сравниваем с искомым JNE REPEAT ;если неравен, то повторяем снова ;иначе, задержка окончена AT имеет добавочную функцию прерывания 15H, которая позволяет осуществить задержку на указанное время. Поместите 86H в AH, а число микросекунд задержки в CX:DX. После этого выполните преры- вание. 2.1.6 Операции запрограммированные во времени. Программа определяет время для выполнения определенной опера- ции в точности так же, как и человек: берется начальное показание счетчика времени суток и затем сравнивается с последующими пока- заниями. Можно получать значения в формате часы-минуты-секунды, но слишком хлопотно вычислять разницу между такими показаниями, поскольку система счета не десятичная. Лучше прямо читать счетчик времени суток BIOS, измерять продолжительность в 1/18 секунды, а затем уже переводить ее в обычный формат чч:мм:сс. 100 GOSUB 500 'получаем значение счетчика 110 START = TOTAL 'сохраняем начальное значение в START . (далее идет процесс, длительность которого измеряется) . 300 GOSUB 500 'получаем финальное значение 310 TOTAL = TOTAL - START 'подсчитываем число импульсов 320 HOURS = FIX(TOTAL/65520) 'вычисляем число часов 330 TOTAL = TOTAL - HOURS*65520 'вычитаем часы из TOTAL 340 MINUTES = FIX(TOTAL/1092) 'вычисляем число минут 350 TOTAL = TOTAL - MINUTES*1092 'вычитаем минуты из TOTAL 360 SECONDS = FIX(TOTAL/18.2) 'вычисляем число секунд 370 PRINT HOURS,MINUTES,SECONDS 'печатаем результат 380 END . . 500 DEF SEG = 0 'подпрограмма чтения времени суток 510 A = PEEK(&H46C) 'получаем младший байт 520 A = PEEK(&H46D) 'получаем следующий байт 530 A = PEEK(&H46E) 'и еще один 540 TOTAL = A + B*256 + C*65535 'подсчитываем результат в TOTAL 550 RETURN 'все сделано Функция TIMER в Бейсике возвращает число секунд, прошедших с момента, когда счетчик времени суток был последний раз установлен в 0. Обычно это число секунд, прошедших со времени последнего включения компьютера. Если при старте системы правильно было установлено системное время, то TIMER возвращает число секунд, прошедших с полуночи. Просто напишите N = TIMER. Средний уровень. Прерывание 1AH имеет две функции для установки (AH = 1) и получения (AH = 0) счетчика времени суток. Для чтения счетчика надо просто выполнить прерывание с AH = 0. При возврате значение счетчика содержится в CX:DX, причем младшее слово в CX. AL содер- жит 0, если счетчик не переходил через границу 24 часов с момента последней установки. Для установки счетчика поместите два слова в те же регистры, а в AH - 1. В приведенном примере измеряются промежутки времени в пределах часа. При этом нужны только два младших байта счетчика. Но в этом случае необходимо проверять, что не было перехода через границу, когда начальное значение было больше, чем следующее. ;---в сегменте данных OLDCOUNT DW 0 ;храним начальное значение счетчика ;---получаем начальное значение счетчика MOV AH,0 ;номер функции INT 1AH ;получаем значение счетчика MOV OLDCOUNT,DX ;сохраняем начальное значение . (здесь идет процесс, длительность которого измеряется) . ;---позднее вычисляем длительность процесса MOV AH,0 ;номер функции INT 1AH ;получаем значение счетчика MOV BX,OLDCOUNT ;считываем старое значение CMP BX,DX ;проверяем на переполнение JG ADJUST ;обработка переполнения SUB DX,BX ;иначе берем разность JMP SHORT FIGURE_TIME ;и переводим ее в обычный вид ;---обработка переполнения ADJUST: MOV CX,0FFFFH ;помещаем в CX максимальное число SUB CX,BX ;вычитаем первое значение ADD CX,DX ;добавляем второе значение MOV DX,CX ;результат храним в DX ;---процедура перевода времени в обычный формат FIGURE_TIME: ;делим на 18.2 секунды и т.д. 2.1.7 Управление работой в реальном времени. При операциях в реальном времени программа выполняет инструк- ции в указанный момент времени, а не при первой возможности. Такого рода операции обычно ассоциируются с роботехникой, но имеется множество других приложений. Имеется выбор подхода к операциям в реальном времени. Для программ, которые не должны ничего делать в промежутке между инструкциями, требующими времен- ной привязки, можно просто периодически проверять счетчик времени суток, ожидая наступления нужного момента. Такой подход практи- чески сводится к набору пустых циклов, описанных в [2.1.5]. Второй подход более сложен. Он используется, когда программа постоянно занята какой-либо работой, но она должна в определенные моменты времени прерывать свои операции для выполнения определен- ной задачи. В этом случае расширяют прерывание таймера, которое выполняется 18.2 раза в секунду. Когда это прерывание происходит, дополнительный код проверяет новое значение счетчика времени суток и если наступил определенный момент времени, запускает нужную процедуру. Этот процесс показан на рис. 2-3. Приведенные здесь простые примеры показывают, как создать в своей программе будильник, который устанавливается пользователем и подает звуко- вой сигнал, когда подошло время. (Более сложный пример низкого уровня в [2.2.6] исполняет музыку, в то время когда процессор занят другими делами.) Высокий уровень. Бейсик обеспечивает примитивный контроль над операциями в реальном времени посредством оператора ON TIMER(n) GOSUB. Когда программа встречает этот оператор, то она начинает отсчитывать n секунд. Тем временем выполнение программы продолжается. Когда n секунд прошло, то программа переходит на подпрограмму, начинаю- щуюся с указанного номера строки, выполняет ее и возвращает уп- равление на то место, откуда была вызвана подпрограмма. После этого отсчет снова начинается с нуля и подпрограмма будет вызвана снова еще через n секунд. ON TIMER не будет функционировать, до тех пор пока он не раз- решен оператором TIMER ON. Оператор TIMER OFF запрещает его рабо- ту. В тех случаях, когда отсчет времени должен продолжаться, но переход на подпрограмму должен быть задержан, надо использовать оператор TIMER STOP. В этом случае отмечается, что n секунд прош- ло, но переход на подпрограмму будет выполенен только после того, как встретится оператор TIMER ON. Поскольку он повторяется, оператор ON TIMER особенно полезен для вывода на экран текущего времени: 100 ON TIMER(60) GOSUB 500 'меняем показания часов каждые 60 110 TIMER ON 'секунд и разрешаем работу таймера . . 500 LOCATE 1,35:PRINT "TIME: ";LEFT$(TIME$,5) 'позиционируем 510 RETURN 'курсор и печатаем время Низкий уровень. BIOS содержит специальное пустое прерывание (1CH), которое ничего не делает, пока Вы не напишите для него процедуру. При старте вектор этого прерывания указывает на инструкцию IRET (возврат из прерывания); при его вызове происходит моментальный возврат. Но прерывание 1CH интересно тем, что оно вызывается прерыванием таймера BIOS после того, как это прерывание обновило значение счетчика времени суток. Можно сказать, что это аппарат- ное прерывание, происходящее автоматически 18.2 раза в секунду. Вы можете изменить вектор этого прерывания так, чтобы он указывал на процедуру в Вашей программе. После этого Ваша процедура будет вызываться 18.2 раза в секунду. О том как написать и установить свою процедуру обработки прерывания см. в [1.2.3]. Написанная Вами процедура должна прочитать только что модифи- цированное значение счетчика времени суток, сравнить его с ожи- даемым временем, и выполнить то что требуется, когда ожидаемое время наконец наступит. Естественно, что когда время еще не по- дошло, то процедура просто возвращает управление, ничего не де- лая. Таким образом, процессор не выполняет лишней работы. В приведенном примере процедура (не показанная здесь) запраши- вает у пользователя число минут (до 60), которое должно пройти до того, как раздастся звонок будильника. Это число, запасенное в MINUTES, умножается на 1092 для перевода в эквивалентное число импульсов счетчика времени суток. Для периода в пределах одного часа достаточно 16 бит - более длинные периоды требуют более сложных 32-битовых операций. Это число импульсов добавляется к младшему слову текущего значения счетчика времени суток и запоми- нается в ALARMCOUNT. Затем вектор прерывания 1CH изменяется таким образом, чтобы он указывал на процедуру ALARM. Помните, что как только вектор будет изменен, ALARM будет автоматически вызываться 18.2 раза в секун- ду. При вызове эта процедура читает текущее значение счетчика времени суток через прерывание 1AH и сравнивает с ALARMCOUNT. При совпадении этих величин вызывается процедура BEEP (также не пока- занная здесь - см. [2.2.4]), которая выдает звуковой сигнал. В противном случае происходит возврат. Обычный код возврата из аппаратных прерываний (MOV AH,20H / OUT 20H,AL) включать в проце- дуру не нужно, так как он будет в прерывании таймера. Будьте внимательны и не забудьте сохранить изменяемые регистры. ;---в сегменте данных MINUTES DW 0 ;хранит число минут до звонка ALARMCOUNT DW 0 ;хранит счетчик времени для звонка ;---установка ожидаемого значения счетчика времени суток CALL REQUEST_MINUTES ;запрос числа минут до звонка MOV AX,MINUTES ;пересылка в AX MOV BX,1092 ;число импульсов счетчика в минуте MUL BX ;умножаем - результат в AX ;получаем текущее значение счетчика MOV AH,0 ;номер функции чтения счетчика INT 1AH ;читаем значение, младший байт в DX ;складываем оба значения ADD AX,DX ; MOV ALARMCOUNT,AX ;получаем нужное значение счетчика ;---заменяем вектор пустого прерывания PUSH DS ;сохраняем сегмент данных MOV AX,SEG ALARM ;берем сегмент процедуры ALARM MOV DS,AX ;помещаем его в DS MOV DX,OFFSET ALARM ;берем смещение процедуры MOV AL,1CH ;номер изменяемого вектора MOV AH,25H ;функция изменения вектора INT 21H ;меняем вектор POP DS ;восстанавливаем сегмент данных ; ;---дальше продолжается программа ; ;---в конце программы возвращаем вектор прерывания MOV DX,0FF53H ;оригинальные значения для MOV AX,0F000H ;прерывания 1CH MOV DS,AX ;помещаем сегмент в DS MOV AL,1CH ;номер изменяемого вектора MOV AH,25H ;номер функции INT 21H ;восстанавливаем вектор ;---процедура выдачи звукового сигнала ALARM PROC FAR ;создаем длинную процедуру PUSH AX ;сохраняем изменяемые регистры PUSH CX ; PUSH DX ; ;---читаем счетчик времени суток MOV AH,0 ;номер функции чтения счетчика INT 1AH ;читаем значение счетчика ;---сравниваем с требуемым значением MOV CX,ALARMCOUNT ;берем требуемое значение CMP DX,CX ;сравниваем с текущим JNE NOT_YET ;если неравны, то на выход ;---выдаем звуковой сигнал, если значения совпали CALL BEEP ;эта процедура не показана ;---иначе возвращаемся из прерывания NOT_YET: POP DX ;восстанавливаем регистры POP CX ; POP AX ; IRET ;возврат из прерывания ALARM ENDP ;конец процедуры 2.1.8 Генерация случайных чисел с помощью микросхемы таймера. Для генерации последовательности случайных чисел требуются сложные математические манипуляции. Но иногда программе в опреде- ленный момент требуется только одно случайное число. В этом слу- чае случайное число может быть получено просто чтением из канала микросхемы таймера. Бейсик использует это число в качестве ядра, по которому генерируется случайная последовательность. Конечно, Вы не можете использовать ряд последовательно считанных значений в качестве случайной последовательности, так как сами по себе интервалы времени между считываниями будут неслучайными. 100 RANDOMIZE TIMER 'сброс генератора случайных чисел 110 PRINT RND,RND,RND 'печать трех случайных чисел в результате получаем: .7122483 .4695052 .9132487 Низкий уровень. Поскольку регистр счетчика канала таймера перезагружается снова и снова данным числом (а в промежутках идет счет вниз до 0), выберите в качестве загружаемого в счетчик значения число, равное требуемому диапазону случайных чисел. Например, для полу- чения случайного значения часа дня загружайте в счетчик 23. Лучше всего использовать режим 3 канала 2 (порт 42H) микросхе- мы таймера [2.1.1]. Сначала установите для счетчика желаемый диапазон случайных чисел (в примере используется 10000, что при- водит к выдаче случайного числа в диапазоне от 0 до 9999). Затем, чтобы получить из канала случайное число, надо подать команду командному регистру микросхемы таймера через порт 43H перенести текущее значение счетчика в регистр "задвижки", для чего надо сбросить биты 4 и 5. Этот перенос в регистр задвижки не мешает продолжающемуся счету. Затем установите оба бита 4 и 5 командного регистра, чтобы процессор мог читать из регистра задвижки. После этого две инструкции IN дадут сначала младший, а затем старший байт в регистре AL. Наконец, восстановите первоначальное значение регистра задвижки, чтобы счет продолжался в пределах указанного диапазона времени. ;---установка адресов портов COMMAND_REG EQU 43H ;адрес командного регистра CHANNEL_2 EQU 42H ;адрес канала 2 CALL SET_COUNT ;установка диапазона . ;---здесь программа работает, а затем требует случайное число . CALL GET_NUMBER ;получение случайного числа . . ;---начинаем отсчет канала 2 SET_COUNT PROC MOV AL,10110110B ;канал 2, режим 2, оба байта OUT COMMAND_REG,AL ;посылаем в командный регистр MOV AX,10000 ;значение счетчика OUT CHANNEL_2,AL ;посылаем младший байт MOV AL,AH ;передвигаем старший байт в AL OUT CHANNEL_2,AL ;посылаем старший байт RET SET_COUNT ENDP ;---получение случайного числа READ_NUMBER PROC ;---пересылаем значение счетчика в регистр задвижки MOV AL,10000110B ;требуемая команда OUT COMMAND_REG,AL ;посылаем в командный регистр ;---читаем значение счетчика MOV AL,10110110B ;запрос на чтение/запись OUT COMMAND_REG,AL ;посылаем запрос IN AL,CHANNEL_2 ;получаем младший байт MOV AH,AL ;временно храним его в AH IN AL,CHANNEL_2 ;получаем старший байт CALL SET_COUNT ;восстанавливаем задвижку SWAP AH,AL ;ставим байты на место RET ;теперь случайное число в AX READ_NUMBER ENDP Раздел 2. Создание звука. Бейсик оснащен достаточно изощренными средствами для генерации звука, однако операционная система позволяет только просто подать звуковой сигнал. Если Вы хотите получить какие-либо сложные зву- ки, то Вы должны прямо программировать микросхему таймера 8253. Канал 2 этой микросхемы прямо связан с динамиком компьютера. Когда этот канал программируется в режиме 3, то он посылает пря- моугольные волны данной частоты. Из-за простоты динамика он сгла- живает края прямоугольной волны, получая более приятную для слуха синусоидальную волну. К сожалению, микросхема 8253 не может ме- нять амплитуду волны, поэтому мы не можем менять громкость звука, издаваемого динамиком. Динамик имеет не один, а два входа для генерации звука. На рис. 2-2 в [2.1.1] показано, что кроме микросхемы таймера, сигнал посылает также микросхема интерфейса с периферией 8255 [1.1.1]. Частота импульсов каждой микросхемы может быть изменена, поэтому комбинируя воздействия этих двух источников мы можем получать специальные звуковые эффекты. Только PCjr имеет специальную микросхему, управляющую генера- тором звука. Он может одновременно выдавать три разных тона, плюс шум для звуковых эффектов. Громкость каждого из трех каналов может устанавливаться независимо. Другой уникальной возможностью PCjr является то, что он может управлять внешним источником зву- ка, таким как кассетный магнитофон. 2.2.1 Программирование генератора звука 76496 (только PCjr). PCjr снабжен 4-канальным генератором звука, в котором три канала генерируют тона, а четвертый служит для генерации шума для звуковых эффектов. Все четыре канала программируются независимо, причем каждый из них имеет свой регулятор громкости, а затем выход со всех них объединяется в единый звуковой сигнал. Исполь- зуется микросхема комплексного генератора звука TI SN76496N. Она имеет 8 регистров - 2 для каждого канала - и все они адресуются через один порт с адресом 0C0H. Этот порт служит только для запи- си; если подать инструкцию IN, то вся система будет заморожена. PCjr имеет также разъем для внешнего источника звука. При старте системы звуковой канал получает выходной сигнал от микрос- хемы таймера 8253. Но этот канал может быть переключен на микрос- хему генератора звука или любой из двух внешних звуковых входов. Это достигается изменением битов 5 и 6 порта B микросхемы интер- фейса с периферией 8255 (адрес порта 61H - см. [1.1.1]). Значение битов следующее: Биты 6 и 5 Выбранная функция 00 микросхема таймера 8253 01 вход с кассетного магнитофона 10 вход канала ввода/вывода 11 генератор звука 76496 Для выбора источника звука в BIOS PCjr добавлена функция 80H прерывания 1AH. Поместите в AL номер кода от 0 до 3, в соответст- вии с вышеприведенной таблицей, и вызовите функцию. Возвращаемых регистров нет. Генератор звука 76496 должен использовать этот звуковой канал, поскольку он не может управлять внутренним дина- миком PCjr. В общем случае, когда байт данных посылается генератору звука, то биты 4-6 содержат код идентификации, сообщающий какому из восьми регистров предназначены данные. Эти коды такие: Биты 6-4 Адресуемый регистр 000 Частота первого тона 001 Громкость первого тона 010 Частота второго тона 011 Громкость второго тона 100 Частота третьего тона 101 Громкость третьего тона 110 Частота четвертого тона 111 Громкость четвертого тона В случае регистров частоты тонов требуются два байта. Значение битов при этом следующее: байт 1: биты 0-3 младшие 4 бита частоты 4-6 код идентификации регистра 7 всегда равен 1 байт 2: биты 0-5 старшие 6 битов частоты 6 не используется 7 всегда равен 0 Для установки частоты тона в регистр посылается 10-битное значе- ние, которое после деления на 111 843 дает желаемую частоту в герцах. Таким образом, доступны частоты, начиная с 110 герц вверх (111 843/2^10). Как только регистр инициализирован (и соответст- венно установлен порт B микросхемы 8255), немедленно начинается звуковой сигнал и продолжается до тех пор, пока он не будет прек- ращен. Не обязательно для изменения частоты посылать новые два байта. Если послан только второй байт (старшие 6 битов частоты), то он автоматически заменяет соответствующие данные в канале, к которому была последняя адресация. Эта возможность позволяет плавно варьировать частоту. Генератору шума для программирования нужен только один байт. Значение битов для него следующее: биты 0-1 плотность шума 2 качество шума 3 не используется 4-6 код идентификации регистра 7 всегда установлен в 1 Качество шума устанавливается на белый шум (постоянное шипение), когда бит 2 равен 1 и на периодический шум (волны звука), когда бит 2 равен 0. Плотность звука увеличивается при увеличении битов 0-1 от 00B до 10B; когда они установлены в 11B, то звук меняется в зависимости от выходного тона канала 3. Громкость каждого из четырех каналов изменяется ослаблением основного сигнала. Для этой установки требуется только один байт. Значение его битов следующее: биты 0-3 ослабление сигнала 4-6 код идентификации регистра 7 всегда установлен в 1 Когда все 4 бита данных равны 0, то громкость максимальна. Когда все они равны 1, то звук полностью подавляется. Для получения звука промежуточной громкости может быть использована любая ком- бинация битов. Бит 0 ослабляет звук на 2 Дб (децибелла), бит 1 - на 4 Дб, бит 2 - на 8 Дб и бит 3 - на 16 Дб. Максимальное ослаб- ление равно 28 Дб. 2.2.2 Генерация тона. Этот подраздел объясняет как производить звук, когда компьютер не занят ничем другим; в [2.2.3] показано как это сделать, когда производятся другие действия. Забавно, но для программистов на ассемблере последнее проще. Для этого достаточно запрограммиро- вать микросхему таймера 8253, которая работает независимо от процессора. В приведенном здесь методе процессор непосредственно управляет динамиком, поэтому программе приходится выполнять рабо- ту, которую может выполнять микросхема таймера. Хотя этот способ более труден, но он допускает существенно больший контроль над динамиком и создание большинства специальных звуковых эффектов [2.2.8] основывается на нем. Высокий уровень. Оператор Бейсика SOUND используется для генерации тона в широ- ком диапазоне частот и длительностей. Частота дается в герцах (от 37 до 32767), а длительность в импульсах счетчика времени суток BIOS (от 0 до 65535), причем в секунду происходит 18.2 импульса. SOUND 440,91 воспроизводит ноту A в течение 5 секунд (5*18.2). Частоты первой октавы, начиная с ноты C(до) таковы: C(до) 523.3 D(ре) 587.3 E(ми) 659.3 F(фа) 698.5 G(соль) 784.0 A(ля) 880.0 B(си) 987.7 Частоты на октаву выше можно получить, удваивая эти значения, на две октавы выше - еще раз удваивая частоты. И наоборот, частоты на октаву ниже равны приблизительно половине этих значений (хоро- шо настроенное пианино точно не следует арифметическим интерва- лам). Благодаря своему генератору звука [2.2.1] PCjr может использо- вать оператор SOUND для трех независимых каналов звука, причем может управляться громкость каждого из них. В этом случае формат оператора: SOUND частота, длительность, громкость, канал. Гром- кость может меняться от 0 до 15, по умолчанию 8. Номер канала может меняться от 0 до 2, по умолчанию 0. Поскольку PCjr может использовать возможности многоголосия и контроля звука только для внешнего динамика, то надо сначала разрешить этот динамик. Это делается с помощью оператора SOUND ON. SOUND OFF передает конт- роль внутреннему динамику. Чтобы сыграть аккорд D-минор (ре-ми- нор) (D-F-A) с малой громкостью, напишите: 100 SOUND ON 'разрешение внешнего динамика 110 SOUND 587,50,3,0 'нота ре 120 SOUND 699,50,3,1 'нота фа 130 SOUND 880,50,3,1 'нота ля Низкий уровень. Генерация звука с помощью адаптера интерфейса с периферией 8255 состоит во включении и выключении с желаемой частотой бита порта B, который связан с динамиком (бит 1). Порт B имеет адрес 61H (хотя AT не имеет микросхемы интерфейса с периферией 8255 как таковой, он использует для этой цели тот же адрес порта и тот же бит). Если программа переключает значение бита с максимально возможной частотой, то частота слишком высокая, чтобы быть полез- ной. Поэтому между двумя переключениями надо вставлять пустой цикл. Помните, что бит 0 порта B управляет воротами канала 2 микросхемы таймера, который в свою очередь связан с динамиком. Поэтому этот бит должен быть сброшен, отсоединяясь от канала таймера. На рис. 2-4 показано как этот метод устанавливает часто- ту звука. В следующем примере введены две переменные. Одна, обозначенная "FREQUENCY", используется в качестве счетчика в пустом цикле между действиями включения и выключения. Чем меньше ее значение, тем быстрее происходит изменение бита и тем больше частота. Пере- менная же "NUMBER_CYCLES" устанавливает продолжительность тона. Она говорит сколько раз должен быть повторен процесс включения и выключения. Чем больше это число, тем дольше звучит данный звук. Отметим, что для этой процедуры аппаратные прерывания должны быть запрещены. Причина этого в том, что прерывание таймера происходит с такой частотой и регулярностью (18.2 раза в секун- ду), что оно будет существенно влиять на частоту. Имейте ввиду, что пока прерывания запрещены, счетчик времени суток BIOS не будет работать. Если затем прочитать его значение, то оно будет отличаться на некоторую величину от реального, до тех пор, пока не будет сделано соответствующее изменение. NUMBER_CYCLES EQU 1000 FREQUENCY EQU 300 PORT_B EQU 61H CLI ;запрет прерываний MOV DX,NUMBER_CYCLES ;длительность тона в DX IN AL,PORT_B ;получаем значение из порта B AND AL,11111110B ;отключаем динамик от таймера NEXT_CYCLE: OR AL,00000010B ;включаем динамик OUT PORT_B,AL ;посылаем команду в порт B MOV CX,FREQUENCY ;задержка на пол-цикла в CX FIRST_HALF: LOOP FIRST_HALF ;делаем задержку AND AL,11111101B ;выключаем динамик OUT PORT_B,AL ;посылаем команду в порт B MOV CX,FREQUENCY ;задержка на пол-цикла в CX SECOND_HALF: LOOP SECOND_HALF ;делаем задержку DEC DX ;вычитаем единицу из счетчика JNZ NEXT_CYCLE ;если 0, то надо кончать STI ;разрешаем прерывания 2.2.3 Генерация звука одновременно с другими действиями. Для программистов на Бейсике различие между этим и предыдущим разделом совершенно несущественно. Но программисты на ассемблере должны использовать совершенно другой метод. Поскольку микросхема таймера 8253 работает независимо от процессора, то очень просто генерировать звук, который издается одновременно с выполнением других операций. Вы должны просто запрограммировать канал 2 этой микросхемы для генерации определенной частоты, а затем перепрог- раммировать микросхему для выключения звука. Высокий уровень. Оператор SOUND в Бейсике не позволяет генерировать звук однов- ременно с другими действиями, но оператор PLAY - позволяет если ему это задать. За оператором PLAY должна следовать строка, кото- рая сообщает какие ноты долны быть сыграны, какой длительности, а также другие характеристики. Детали командной строки PLAY обсуж- даются в [2.2.5]. Если строка содержит буквы MB (фоновая музыка), то строка помещается в специальный буфер и выполняется одновре- менно с другими программными действиями. Напротив, MF (музыка на переднем плане) останавливает все программные операции до тех пор, пока вся строка не будет исполнена. Вот как исполнить одну ноту A (ля) в фоновом режиме: 100 PLAY "MB A" 'исполняется нота ля... 110 ...... 'и следующие операторы программы Отметим, что в фоновом режиме, оператор X = PLAY(0) возвращает число нот (до 32), которое осталось сыграть. В многоканальном режиме на PCjr возвращается число нот в буфере данного канала (0-2), номер которого указан в скобках. Низкий уровень. Просто пошлите счетчик в канал 2, как объяснено в [2.1.1]. Микросхема должна быть предварительно разрешена через порт B микросхемы интерфейса с периферией 8255 (адрес 61H). Вычислите требуемое значение счетчика для задвижки, разделив 1.19 миллионов на требуемую частоту в герцах. Звук будет продолжаться до тех пор, пока не будут закрыты ворота канала 2. Поэтому Вы должны сбросить бит 1 порта B в 0, иначе звук будет продолжаться беско- нечно и может быть прекращен только перезагрузкой компьютера. Для точного регулирования длительности звука можно использовать счет- чик времени суток BIOS, как указано в [2.1.6]. В данном примере генерируется частота 440 герц. Звук прекращается после нажатия любой клавиши на клавиатуре. ;---рарешение канала 2 установкой порта B микросхемы 8255 PORT_B EQU 61H ;установка адреса порта B IN AL,PORT_B ;чтение его значения OR AL,3 ;установка двух младших битов OUT PORT_B,AL ;посылаем байт в порт B ;---установка регистров ввода/вывода COMMAND_REG EQU 43H ;адрес командного регистра CHANNEL_2 EQU 42H ;адрес канала 2 MOV AL,10110110B ;цепочка битов для канала 2 OUT COMMAND_REG,AL ;засылка в командный регистр ;---засылка счетчика в задвижку MOV AX,2705 ;счетчик = 1190000/440 OUT CHANNEL_2,AL ;посылаем младший байт MOV AL,AH ;сдвигаем младший байт в AL OUT CHANNEL_2,AL ;посылаем старший байт ;---ждем нажатия клавиши MOV AH,1 ;номер функции прерывания 21H INT 21H ;вызываем прерывание ;---выключение звука IN AL,PORT_B ;получаем байт из порта B AND AL,11111100B ;сбрасываем два младших бита OUT PORT_B,AL ;посылаем байт обратно 2.2.4 Гудок динамика. Некоторым программам требуется набор предостерегающих гудков. Их легко создавать на Бейсике, но операционная система не обеспе- чивает функцию гудка, как таковую, и только косвенно позволяет получать доступ к гудку, который Вы слышите при старте системы. Для изменения тона вся процедура генерации звука должна быть запрограммирована на низком уровне. Для того чтобы гудок соот- ветствовал подаваемому им сигналу необходимо проявить воображе- ние. Для предсказания близкой опасности создайте набор понижаю- щихся тонов [2.2.7] или, если принтер включен, чередуйте гудки динамика компьютера и принтера (вывод кода ASCII 7 на принтер). Высокий уровень. В Бейсике просто напишите BEEP. Вот кусочек кода, который реагирует на вероятную ошибку гудком и запросом: 100 INPUT "Enter your age",AGE 'запрос возраста 110 IF AGE > 100 THEN BEEP:PRINT"Are you really over 100?" Для гудков другой частоты и продолжительности используйте оператор SOUND. Его форма: SOUND частота, длительность , где частота дается в герцах (3000 - середина диапазона), а длитель- ность дается в восемнадцатых долях секунды. SOUND 3000,18 дает гудок длительностью около одной секунды. В нижеприведенном приме- ре динамик быстро переходит от высокого тона к низкому и обратно, распугивая все живое в ближайшей окрестности. 100 FOR N = 1 TO 200 'установка числа повторений 110 SOUND 500,1 'звук низкой частоты на 1 секунду 120 SOUND 5000,1 'звук высокой частоты на 1 секунду 130 NEXT 'повтор Средний уровень. Операционная система не предоставляет специальной функции для генерации звука. Но Вы можете вызвать знакомый гудок просто пода- вая код ASCII 7 на стандартное устройство вывода (т.е. терминал), используя одну из функций DOS или BIOS. Код ASCII 7 интерпрети- руется как управляющий символ "звонок" и он не рисуется на экра- не. Проще всего использовать функцию 2 прерывания 21H: MOV AH,2 ;функция вывода символа на экран MOV DL,7 ;посылаем код ASCII 7 INT 21H ;динамик гудит Низкий уровень. Для простого гудка лучше всего подходит метод, основанный на использовании микросхемы интерфейса с периферией 8255 [1.1.1]. Ниже приведен пример, который практически повторяет гудок, кото- рый Вы слышите при старте системы. ;---гудок динамика MOV DX,800 ;счетчик числа циклов IN AL,61H ;читаем порт B 8255 AND AL,0FEH ;выключаем бит таймера 8253 NEXTCYCLE: OR AL,2 ;включаем бит динамика OUT 61H,AL ;посылаем байт в порт B MOV CX,150 ;длительность первой половины CYCLEUP: LOOP CYCLEUP ;задержка пока сигнал высокий AND AL,0FDH ;выключаем бит динамика OUT 61H,AL ;посылаем байт в порт B CYCLEDOWN: LOOP CYCLEDOWN ;задержка пока сигнал низкий DEC DX ;уменьшаем счетчик циклов JNZ NEXTCYCLE ;повторяем цикл пока DX не 0 2.2.5 Генерация набора тонов. В этом подразделе показано как генерировать цепочку звуков, когда компьютер ниче