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

ОГЛАВЛЕНИЕ

      Раздел 1. Установка и чтение таймера.
        Программирование микросхемы таймера 8253/8254.
        Установка/чтение времени.
        Установка/чтение даты.
        Установка/чтение часов реального времени.
        Задержка программных операций.
        Операции запрограммированные во времени.
        Управление работой в реальном времени.
        Генерация случайных чисел с помощью микросхемы таймера.
      Раздел 2. Создание звука.
        Программирование генератора звука 76496 (только PCjr).
        Генерация тона.
        Генерация звука одновременно с другими действиями.
        Гудок динамика.
        Генерация набора тонов.
        Генерация строки тонов, одновременно с другими операциями.
        Создание плавного перехода тонов.
        Создание звуковых эффектов.
        Одновременная генерация разных звуков.

Глава 2. Таймеры и звук.

     Раздел 1. Установка и чтение таймера.


   Микросхема 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                  День месяца
                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 Генерация набора тонов.


   В  этом  подразделе показано как генерировать цепочку  звуков,
когда компьютер ничем другим не занят; в следующем будет показано
как выполнить ту же задачу, когда компьютер занят другой работой.
Когда компьютер ничем другим не занят,  то можно выводить мелодию
или производить специальные звуковые эффекты; когда же  компьютер
занят другой работой, то нельзя производить звуковые эффекты.
   Создание звуковых строк является одной из мощнейших возможнос-
тей, предоставляемых Бейсиком.  Построение же строк звуков в  ас-
семблере требует большой работы.  Может быть использован любой из
двух  методов генерации звука, предложенных в [2.2.2] и  [2.2.3].
Для обоих методов надо  просто  генерировать  один  тон в течении
заданного времени, затем следующий и т.д.  Каждая звуковая строка
формируется из двух строк данных, одна из которых содержит часто-
ты  последовательных тонов, а другая хранит их длительности  (при
условии, что требуются разные  длительности).   Продолжительность
звучания  определяется  с использованием счетчика  времени  суток
BIOS [2.1.6].

   Высокий уровень.


   Опреатор Бейсика PLAY предоставляет большие возможности.  Опе-
ратор  сопровождается строкой нот, перемешанных с  информацией  о
том, как эти ноты должны быть исполнены. Ноты записываются буква-
ми A - G и последующими знаками для диезов и бемолей. Диезы обоз-
начаются знаками # или +, а  бемоли  минусом (-).  Операторы PLAY
"CC#D" и PLAY "CD-D" эквивалентны, но нельзя использовать диезы и
бемоли для обозначения белых  клавиш.   Второй способ задания нот
состоит  в вычислении кодового номера от 0 до 84, причем 0  соот-
ветствует отсутствию звучания,  а  числа от 1 до 84 соответствуют
84 возможным нотам семи октав, начиная снизу. Номеру должна пред-
шествовать буква N: PLAY "N3N72N44".
   Допустимый диапазон -  семь  октав,  внутри  каждой могут быть
ноты от C(до) до B(си).  Октавы пронумерованы от 0 до 6 и нота до
первой октавы соответствует  октаве  3. Текущая октава может быть
изменена  в  любой момент, за счет вставки в строку буквы  O,  за
которой следует номер октавы.  Если  не было начальной установки,
то  используется октава 4.  Оператор PLAY "O3CO4CO5CO6C"  выводит
ноты до последовательных октав  вверх.   Другой  способ изменения
октавы  состоит во включении в строку символов > или  <,  которые
переключают тон вверх и вниз на октаву, соответственно.  Оператор
PLAY "O3C>C>C>C" приводит к тому же результату, что и предыдущий.
   Длительность исполнения нот также может быть изменена за  счет
вставки кодового номера, которому предшествует буква L.  Все пос-
ледующие ноты будут исполняться с этой длительностью до тех  пор,
пока не встретится другой код длины.  Код - это число от 1 до 64,
причем  1 соответствует целой ноте, а 64 - 1/64.  Запись L4 соот-
ветствует четверти.  Темп с которым исполняются ноты регулируется
кодом темпа, который состоит из буквы T, за которой следует число
от 32 до 255, дающее число четвертей, исполняемых в минуту.  Если
эти параметры не указаны, то по умолчанию берется длительность L4
и темп 120.  Для  изменения  длительности  только одной ноты надо
поместить значение длины после ноты и без буквы L.  Оператор PLAY

"L4CDE16FG" исполнит E как шестнадцатую, а все остальные ноты как
четверти.  Длительность пауз берется такой же, как и длительность
нот.  Поместите номер от 1 до 64 после буквы P для паузы.  P1 де-
лает  паузу интервалом в целую, а P64 - в 1/64.  Помещение  точки
после ноты имеет тот же эффект,  какой  он  имеет в обычной музы-
кальной  нотации:  длительность  ноты  увеличивается  наполовину.
Вторая точка продолжит длительность еще наполовину.
   По умолчанию ноты играются 7/8 указанной  длительности.  Чтобы
они исполнялись полную длительность (легато), поместите в  строку
ML. Чтобы они исполнялись 3/4  длительности (стаккато), поместите
в строку MS. Чтобы вернуться к нормальному стилю надо указать MN.
   Обычно,  вся прочая деятельность программы прекращается до тех
пор, пока не будет  сыграна  строка.  Для  того чтобы выполнялись
операторы,  следующие за оператором PLAY, а строка исполнялась  в
фоновом режиме, поместите в  строку  MB.  Для восстановления нор-
мальной ситуации напишите MF.
   Наконец,  оператор  PLAY позволяет исполнять подстроки  внутри
длинной строки.  Имеется  в  виду,  что  часть исполняемой строки
может быть введена как обычная строковая переменная, а затем  эта
переменная может быть вызвана из строки сформированной в операто-
ре  PLAY.   Например,  если S$ =  "EEEEE",  то в  операторе  PLAY
"CDXS$;FG" нота E будет повторена 5 раз. Отметим, что имени пере-
менной должна предшествовать буква X, а за именем следовать точка
с запятой (;).  (Для  компилируемых  программ  применяется другой
метод, использующий переменную VARPTR$ - детали см. в руководстве
по Бейсику).

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

   100 PLAY "ML T40 O3 ECD<G P32 G>DEC"

   Благодаря наличию генератора звука PCjr добавляет к  оператору
PLAY две возможности. Во-первых, допускается параметр V, устанав-
ливающий  громкость.  Выражение V5 устанавливает  (или  изменяет)
громкость на уровень 5. Допустимый диапазон от 0 до 15, причем по
умолчанию  берется 8.  0 полностью подавляет звук.  Во-вторых,  с
помощью оператора PLAY можно  одновременно исполнять три звуковых
строки.  Поместите все три строки в одну программную строку, раз-
деляя их запятыми.  Для того чтобы иметь возможность использовать
эти  специальные  свойства,  Вы должны  предварительно  разрешить
внешний динамик с помощью оператора SOUND ON.

   100 SOUND ON
   110 PLAY "...........","..........","............"

   Низкий уровень.


   В примере для генерации звука используется  микросхема таймера
8253.   Здесь просто исполняются 8 нот, но небольшая  модификация
может сильно расширить  возможности  этой процедуры.  Имеется три
строки данных. Первая устанавливает длительность каждой ноты, как
кратное произвольного периода  задержки  (изменяя этот период за-

держки,  можно  изменять темп).  Вторая строка  содержит  частоты
каждой из 8 нот;  эти  значения  должны  быть  помещены в регистр
задвижки  канала 2 микросхемы 8253 для исполнения желаемых тонов.
Третья строка содержит мелодию в виде  кодовых номеров от 1 до 8,
которые  соответствуют  восьми частотам.  Эта строка  завершается
кодом 0FFH, который  служит  признаком  конца  мелодии. Процедура
просто  читает  очередную ноту мелодии,  находит  соответствующую
частоту и помещает ее в канал 2. Затем длительность для этой ноты
помещается  в счетчик цикла задержки, который использует  счетчик
времени суток, а когда задержка  кончается,  то переходим к обра-
ботке следующей ноты. На рис. 2-5 показана работа этой процедуры.

;---в сегменте данных
BEAT        DB   10,9,8,7,6,5,4,3,2    ;длительность нот
FREQUENCY   DW   2280,2031,1809,1709   ;таблица частот
            DW   1521,1353,1207,1139   ;
MELODY      DB   1,2,3,4,5,6,7,8,0FFH  ;номер частоты ноты

;---инициализация
PORT_B      EQU  61H
COMMAND_REG EQU  43H
LATCH2      EQU  42H
            IN   AL,PORT_B      ;получаем текущий статус
            OR   AL,00000011B   ;разрешаем динамик и таймер
            OUT  PORT_B,AL      ;заменяем байт
            MOV  SI,0           ;инициализируем указатель
            MOV  AL,0B6H        ;установка для канала 2
            OUT  COMMAND_REG,AL ;посылаем в командный регистр
;---смотрим ноту, получаем ее частоту и помещаем в канал 2
NEXT_NOTE:  LEA  BX,MELODY      ;берем смещение для мелодии
            MOV  AL,[BX][SI]    ;берем код n-ной ноты строки
            CMP  AL,0FFH        ;проверка на конец строки
            JE   NO_MORE        ;если конец, то на выход
            CBW                 ;переводим в слово
   ;получение частоты
            MOV  BX,OFFSET FREQUENCY  ;смещение таблицы частот
            DEC  AX             ;начинаем отсчет с 0
            SHL  AX,1           ;умножаем на 2, т.к. слова
            MOV  DI,AX          ;адресуем через DI
            MOV  DX,[BX][DI]    ;получаем частоту из таблицы
   ;начинаем исполнение ноты
            MOV  AL,DL          ;готовим младший байт частоты
            OUT  LATCH2,AL      ;посылаем его
            MOV  AL,DH          ;готовим старший байт частоты
            OUT  LATCH2,AL      ;посылаем его
;---создание цмкла задержки
            MOV  AH,0           ;номер функции чтения счетчика
            INT  1AH            ;получаем значение счетчика
            MOV  BX,OFFSET BEAT ;смещение таблицы длин
            MOV  CL,[BX][SI]    ;берем длину очередной ноты
            MOV  CH,0           ;
            MOV  BX,DX          ;берем младшее слово счетчика
            ADD  BX,CX          ;определяем момент окончания

STILL_SOUND: INT 1AH            ;берем значение счетчика
            CMP  DX,BX          ;сравниваем с окончанием
            JNE  STILL_SOUND    ;неравны - продолжаем звук
            INC  SI             ;переходим к следующей ноте
            JMP  NEXT_NOTE      ;
;---завершение
NO_MORE:    IN   AL,PORT_B      ;получаем статус порта B
            AND  AL,0FCH        ;выключаем динамик
            OUT  61H,AL         ;заменяем байт


     2.2.6 Генерация строки тонов, одновременно с другими операциями.


   Хотя в Бейсике это  делается  очень  просто, на самом деле это
нетривиальный трюк программирования в реальном времени. Для реше-
ния этой задачи нужно  использовать генерацию звука через микрос-
хему  8253  [2.2.3], так как метод, использующий микросхему  8255
[2.2.2], занимает процессор. Соответственно, только строки чистых
музыкальных  тонов  могут производиться таким  методом -  всякого
рода звуковые эффекты при этом недоступны. Основная техника прог-
раммирования  в реальном времени показана в [2.1.7].   Программы,
работающие в реальном времени,  модифицируют  прерывание таймера,
которое  останавливает процессор 18.2 раз в секунду, чтобы  изме-
нить показание счетчика времени суток.  Расширение процедуры пре-
рывания сравнивает новое значение счетчика времени суток со  зна-
чением, показывающим время завершения генерации тона, и когда это
значение  достигнуто, прерывает звук, начинает генерацию  другого
тона и устанавливает время его окончания.

   Высокий уровень.


   Генерация  строки  звуков  одновременно  с  другими операциями
является  одной  из  возможностей очень мощного  оператора  PLAY,
который детально обсуждался  в  [2.2.5].  Надо  просто добавить в
начало управляющей строки MB.  Это сокращение от Music Background
(фоновая музыка); для того  чтобы  заставить  PLAY прекратить все
другие операции, пока генерация звуковой строки не будет заверше-
на, вставьте MF.  В  нижеприведенном примере во время рисования и
заполнения  рамки  исполняется  гамма (для его  работы  требуется
наличие графических возможностей).

100 PLAY "MB T100 O3 L4;CDEFG>ABC"  'исполняем набор нот
110 LINE (10,10)-(80,80),1,BF       'одновременно рисуем рамку

   Низкий уровень.


   Приведенная процедура является развитием процедуры, показанной
в предыдущем разделе, на случай  реального  времени.  Она требует
понимания, как перепрограммировать прерывание таймера, что обсуж-
далось в [2.1.7]. На эту процедуру должен указывать вектор преры-
вания  и  тогда она будет выполняться 18.2 раза в  секунду, в  те
моменты, когда будет обновляться  значение счетчика времени суток
BIOS. Обычно, будут выполняться только несколько строчек, которых
достаточно, чтобы  определить,  что  время изменения звука еще не
наступило,  - и процедура освождает процессор для решения  других
задач.
   Счетчик времени суток BIOS используется для измерения длитель-
ности каждой ноты.  При переходе от одной ноты к другой, длитель-
ность новой ноты вычисляется  как  число импульсов счетчика и это
значение  добавляется  к текущему его значению.  Каждый  раз  при
вызове процедуры проверяется  текущее  значение  счетчика времени
суток, и когда ожидаемое время наконец наступает, то  выполняется
набор операций по поиску новой  ноты, программированию ее частоты
в  канале  2 микросхемы 8253 и установлению нового счетчика  дли-
тельности.  Добавочный  код  требуется  для обработки специальных
случаев первой и последней нот в строке.

;---в сегменте данных
BEAT        DB   10,9,8,7,6,5,4,3,2   ;длительность нот
FREQUENCY   DW   2280,2031,1809,1709  ;таблица частот
            DW   1521,1355,1207,1139  ;
MELODY      DB   1,2,3,4,5,6,7,8,0FFH ;номер частоты в таблице
HOLDIP      DW   0                    ;запоминаем оригинальный
HOLDCS      DW   0                    ;вектор прерывания
SOUND_NOW?  DB   1                    ;звук включен?
FIRST_NOTE? DB   1                    ;первая нота?
END_NOTE    DW   0                    ;счетчик конца ноты
WHICH_NOTE  DW   0                    ;указатель на текущую ноту
;---инициализация вектора прерывания
   ;изменение вектора
   PUSH  DS                      ;сохраняем регистр
   MOV   AX,SEG MELODY2          ;сегмент процедуры
   MOV   DS,AX                   ;помещаем в DS
   MOV   DX,OFFSET MELODY2       ;смещение процедуры
   MOV   AL,1CH                  ;номер вектора прерывания
   MOV   AH,25H                  ;функция установки вектора
   INT   21H                     ;изменение вектора
   POP   DS                      ;восстановление регистра
;
;---программа работает дальше, постоянно вызывая процедуру
;
;---в конце программы восстанавливаем вектор прерывания
   MOV   DX,0FF53H        ;восстанавливаем оригинальные
   MOV   AX,0F000H        ;значения для вектора 1CH
   MOV   DS,AX            ;
   MOV   AL,1CH           ;номер прерывания
   MOV   AH,25H           ;функция установки вектора
   INT   21H              ;восстанавливаем вектор
   RET                    ;

;---это само прерывание
MELODY2    PROC FAR
           PUSH AX        ;сохраняем изменяемые регистры
           PUSH BX        ;
           PUSH CX        ;
           PUSH DX        ;
           PUSH DI        ;
           PUSH SI        ;
           PUSH DS        ;
           MOV  AX,SS:[114]   ;берем начальный DS со стека
           MOV  DS,AX         ;восстанавливаем его
           CMP  SOUND_NOW?,1  ;нужен ли звук?
           JE   PLAY_IT       ;если нет, то выход из прерывания
           JMP  NOT_NOW       ;
PLAY_IT:   CMP  FIRST_NOTE?,0 ;это первая нота?
           JE   TIME_CHECK    ;если нет, то на установку времени

;---инициализация
PORT_B        EQU  61H           ;определяем имена портов
COMMAND_REG   EQU  43H           ;
LATCH2        EQU  42H           ;
              IN   AL,PORT_B     ;берем статус порта B
              OR   AL,00000011B  ;разрешаем динамик и таймер
              OUT  PORT_B,AL     ;посылаем байт обратно
              MOV  SI,0          ;указатель на строки
              MOV  AL,0B6H       ;инициализация канала 2 таймера
              OUT  COMMAND_REG,AL   ;посылаем в командный регистр
              MOV  FIRST_NOTE?,0    ;сбрасываем флаг первой ноты
;---ищем ноту, получаем ее частоту, посылаем в канал 2
NEXT_NOTE:    LEA  BX,MELODY     ;берем смещение строки мелодии
              MOV  SI,WHICH_NOTE ;указатель на текущую ноту
              MOV  AL,[BX][SI]   ;код текущей ноты строки
              CMP  AL,0FFH       ;проверяем признак конца
              JE   NO_MORE       ;если да, то на конец
              CBW                ;иначе в словный формат
   ;получаем частоту
              MOV  BX,OFFSET FREQUENCY  ;смещение таблицы частот
              DEC  AX            ;начинаем отсчет с нуля
              SHL  AX,1          ;умножаем на 2, т.к. словная
              MOV  DI,AX         ;адресуемся через DI
              MOV  DX,[BX][DI]   ;получаем частоту из таблицы
   ;начинаем исполнение ноты
              MOV  AL,DL         ;готовим младший байт частоты
              OUT  LATCH2,AL     ;посылаем в регистр задвижки
              MOV  AL,DH         ;готовим старший байт
              OUT  LATCH2,AL     ;посылаем его
;---пустой цикл, определяющий длительность нот
TIME_IT:      MOV  AH,0          ;фнукция чтения счетчика
              INT  1AH           ;получаем значение счетчика
              MOV  BX,OFFSET BEAT  ;смещение строки длин нот
              MOV  CL,[BX][SI]   ;длительность текущей ноты
              MOV  CH,0          ;
              MOV  BX,DX         ;младшее слово значения счетчика
              ADD  BX,CX         ;добавляем длину в импульсах
              MOV  END_NOTE,BX   ;запоминаем время окончания
TIME_CHECK:   MOV  AH,0          ;функция чтения счетчика
              INT  1AH           ;читаем счетчик
              CMP  DX,END_NOTE   ;сравниваем с нужным
              JNE  NOT_NOW       ;если неравно, то выходим
              MOV  SI,WHICH_NOTE ;иначе, берем следующую ноту
              INC  SI            ;увеличиваем номер ноты
              MOV  WHICH_NOTE,SI ;запоминаем его
              JMP  NEXT_NOTE     ;начинаем следующую ноту
;---завершение процедуры
NO_MORE:      IN   AL,PORT_B     ;берем статус порта B
              AND  AL,0FCH       ;выключаем динамик
              OUT  61H,AL        ;возвращаем байт
              MOV  SOUND_NOW?,0  ;восстанавливаем переменные
              MOV  FIRST_NOTE?,1 ;

NOT_NOW:      POP  DS            ;восстанавливаем регистры
              POP  SI            ;
              POP  DI            ;
              POP  DX            ;
              POP  CX            ;
              POP  BX            ;
              POP  AX            ;
              IRET               ;возврат из прерывания
MELODY2       ENDP


     2.2.7 Создание плавного перехода тонов.


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

   Высокий уровень.


   В Бейсике надо просто поместить оператор SOUND [2.2.2] в цикл,
используя очень малые длины тонов. При каждом новом проходе цикла
надо увеличивать частоту.  Смотрите  [2.2.8], где приведен пример
использования оператора PLAY для более быстрых переходов.

100 FOR N = 1 TO 500 STEP 15
110 SOUND 400 + N,1
120 NEXT

   Низкий уровень.


   Проще  всего использовать метод генерации  звука,  управляемый
микросхемой интерфейса с периферией 8255. Просто меняйте значение
бита  1 порта B между 0 и 1, используя для отсчета времени пустой
цикл, как показано в [2.2.2].   При начале каждого нового пустого
цикла,  засчет засылки значения в CX, слегка изменяйте это значе-
ние. Здесь тон повышается:

;---запрет микросхемы таймера
PB       EQU  61H        ;адрес порта B микросхемы 8255
         IN   AL,PB      ;получаем из него байт
         OR   AL,1       ;сбрасываем бит 0
         OUT  PB,AL      ;возвращаем байт в порт
;---установка частоты и длительности звука
         MOV  BX,9000    ;начальное значение счетчика
         MOV  DX,3000    ;длительность звука 3000 циклов
REPEAT:                  ;сюда возвращаемся после цикла
;---установка бита динамика
         OR   AL,00000010B   ;устанавливаем бит 1
         OUT  PB,AL          ;посылаем байт в порт B
         MOV  CX,BX          ;установка счетчика для 1/2 цикла
CYCLE1:  LOOP CYCLE1         ;пустой цикл на 1000 повторов
;---сброс бита динамика
         AND  AL,11111101B   ;сбрасываем бит 1
         OUT  PB,AL          ;посылаем байт в порт
         MOV  CX,BX          ;установка счетчика
CYCLE2:  LOOP CYCLE2         ;пустой цикл
;---переход к следующему циклу
         DEC  BX             ;увеличиваем частоту, уменьшая
         DEC  BX             ;счетчик
         DEC  DX             ;уменьшаем оставшуюся длительность
         JNZ  REPEAT         ;если DX не 0, то новый цикл

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


     2.2.8 Создание звуковых эффектов.


   Звуковые  эффекты  обычно достигаются  непрерывным  изменением
частоты тона. Только PCjr  достаточно  хорошо оборудован для этой
цели (см. обсуждение в [2.2.1]).  На других машинах нельзя произ-
водить звуковые эффекты одновременно с другими операциями.

   Высокий уровень.


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

100 FOR N = 1 TO 100     'установка длительности
110 PLAY "L64 T255"      'самый быстрый темп
120 PLAY "O1A"           'выдаем низкое A
130 PLAY "O5A"           'выдаем высокое A
140 NEXT                 'повтор

При изменении частоты всего на несколько герц получаем вибрацию:

100 FOR N = 1 TO 100     'установка длительности
110 SOUND 440,1          'выдаем ноту A
120 SOUND 445,1          'немного меняем частоту
130 NEXT                 'повтор

Другая  техника  заключается во вложении плавно меняющихся  тонов
внутрь последовательности, которая  сама гуляет по частотам вверх
или вниз.  На рис.  2-6 показана движущаяся вверх  последователь-
ность. Многие игры с лабиринтами используют эту технику:

100 FOR I = 1 TO 10   'число повторений
110 FOR J = 1 TO 6    'число разных октав
120 PLAY "MBL64T255O=J;BA#AG#GF#FED#DC#CC#DD#EFF#GG#AA#B"
130 NEXT              'повтор в более высокой октаве
140 NEXT              'повтор всей последовательности

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

   NOISE источник, громкость, длительность

Источник  -  это число от 0 до 7, значение которого  приведено  в
таблице:

   0       периодический шум в высоком диапазоне
   1       периодический шум в среднем диапазоне
   2       периодический шум в низком диапазоне
   3       периодический шум, диапазон меняется с каналом 3
   4       белый шум в высоком диапазоне
   5       белый шум в среднем диапазоне
   6       белый шум в низком диапазоне
   7       белый шум, диапазон меняется с каналом 3
Громкость задается числом от  0  до  15,  где 0 соответствует от-
сутствию звука. Длительность указывается числом импульсов счетчи-
ка времени суток, которые отсчитываются 18.2 раза в секунду.

   Низкий уровень.


   Любой из способов, показанных на Бейсике может быть реализован
на  ассемблере, хотя, как видно из предыдущих разделов, это  тре-
бует затрат на программирование.  Кроме того, ассемблер позволяет
генерировать  нечистые  тона, когда интервал, в течение  которого
динамик включен, не равен интервалу, в течение которого он выклю-
чен.  Такое нарушение симметрии может приводить к жужжащим и бря-
кающим звукам. Когда отношение этих интервалов составляет, скажем
50  к 1, то получаем жужжание.  Если увеличить отношение еще в 10
- 20 раз, то жужжание переходит  в  отдельные  брякающие звуки. В
любом  случае звук генерируется микросхемой интерфейса с  перифе-
рией 8255, с помощью  техники  показанной  в  [2.2.2]. Вот пример
жужжания:

NUMBER_CYCLES  EQU  300     ;число переключений динамика
FREQUENCY1     EQU  50      ;время, когда динамик включен
FREQUENCY2     EQU  3200    ;время, когда динамик выключен
PORT_B         EQU  61H     ;адрес порта B микросхемы 8255
            CLI                  ;запрет прерываний
            MOV  DX,NUMBER_CYCLES;DX считает длину тона
            IN   AL,PORT_B       ;получаем статус порта
            AND  AL,11111110B    ;отключаем динамик от таймера
NEXT_CYCLE: OR   AL,00000010B    ;включаем динамик
            OUT  PORT_B,AL       ;посылаем команду
            MOV  CX,FREQUENCY1   ;задержка для первой части
FIRST_HALF: LOOP FIRST_HALF      ;
            AND  AL,11111101B    ;выключаем динамик
            OUT  PORT_B,AL       ;посылаем команду
            MOV  CX,FREQUENCY2   ;задержка для второй части
SECND_HALF: LOOP SECND_HALF      ;
            DEC  DX              ;уменьшаем число циклов
            JNZ  NEXT_CYCLE      ;если 0, то пора кончать
            STI                  ;разрешаем прерывания

Для  создания брякающих звуков можно использовать этот же код, но
надо заменить значение FREQUENCY2 на величину около 40000.


     2.2.9 Одновременная генерация разных звуков.


   Только микросхема генератора  звука,  имеющаяся в PCjr, позво-
ляет  одновременно генерировать разные звуки (см.   обсуждение  в
[2.2.1]). Однако ассемблер позволяет объединить два способа гене-
рации  звука,  что создает имитацию одновременной генерации  двух
разных звуков.  Интерференция этих двух сигналов приводит к слож-
ной  форме звуковой волны.  Каждый из двух звуков  имеет  меньшую
громкость, поэтому в результате  получается  скорее жужжание, чем
два разных голоса. Этот прием реально полезен только для создания
звуковых эффектов.

   Низкий уровень.


   Надо просто объединить два метода  генерации звука, показанные
в [2.2.2] и [2.2.3]. Начните звук через канал 2 микросхемы тайме-
ра.  Затем модулируйте  выход  динамика,  за  счет бита 1 порта B
микросхемы  интерфейса с периферией.  Второе действие  определяет
продолжительность звука. Не забудьте выключить микросхему таймера
при завершении.

;---начинаем генерацию звука через канал 2 таймера
      IN   AL,61H          ;получаем байт из порта B
      OR   AL,3            ;устанавливаем младшие два байта
      OUT  61H,AL          ;посылаем байт обратно
      MOV  AL,10110110B    ;цепочка для командного регистра 8253
      OUT  43H,AL          ;посылаем в регистр
      MOV  AX,600H         ;счетчик для канала 2
      OUT  42H,AL          ;посылаем младший байт
      MOV  AL,AH           ;готовим старший байт
      OUT  42H,AL          ;посылаем старший байт
;---генерируем вторую частоту микросхемой 8255
NUMBER_CYCLES  EQU  9000           ;число переключений
FREQUENCY      EQU  150            ;задержка для половины цикла
               CLI                 ;запрет прерываний
               MOV  DX,NUMBER_CYCLES  ;DX считает длину тона
               IN   AL,61H         ;получаем статус порта
               AND  AL,11111111B   ;отключаем динамик от таймера
NEXT_CYCLE:    OR   AL,00000010B   ;включаем динамик
               OUT  61H,AL         ;посылаем назад в порт
               MOV  CX,FREQUENCY   ;задержка на 1/2 цикла
FIRST_HALF:    LOOP FIRST_HALF     ;
               AND  AL,11111101B   ;выключаем динамик
               OUT  61H,AL         ;посылаем команду в порт
               MOV  CX,FREQUENCY   ;задержка на 1/2 цикла
SECOND_HALF:   LOOP SECOND_HALF    ;
               DEC  DX             ;меняем счетчик циклов
               JNZ  NEXT_CYCLE     ;если 0, то пора кончать
               STI                 ;разрешаем прерывания
;---выключение канала 2 микросхемы таймера
               IN   AL,61H         ;получаем статус порта
               AND  AL,11111100B   ;сбрасываем 2 младших бита
               OUT  61H,AL         ;посылаем байт обратно