Время суток
Время суток
Канал 0 таймера 8253 имеет специальное назначение в IBM PC. Выход
этого канала таймера подключен к уровню прерывания 0 микросхемы
8259. Это означает, что всякий раз, когда выход канала 0 имеет
активный уровень сигнала, возникает прерывание (при условии, что
все остальное установлено корректно). Процедура самопроверки при
включении питания инициализирует канал 0 таймера, загружая в него
число 0. Это дает наибольшее (не наименьшее) значение счетчика,
которое может записать в него программа. Имея на входе частоту 1.19
МГц, счетчик считает обратно к нулю чуть быстрее, чем за 55
миллисекунд. Программа инициализации устанавливает таймер таким
образом, что он считает непрерывно. Это означает, что прерывание 0
возникает 18.2 раза в секунду.
Как мы увидим в следующей главе, встроенная система программ
BIOS использует это постоянное прерывание от таймера, чтобы следить
за текущим временем. BIOS продвигает часы текущего времени вперед
всякий раз, когда возникает прерывание. Затем, с помощью
соответствующих вычислений, Вы можете преобразовать число циклов
таймера в часы, минуты и секунды.
Почему же выбрано значение 18.2? Почему счетчик не
программируется так, чтобы давать прерывание 20 раз в секунду, или
другое "хорошее" число раз? Это объясняет следующий пример.
Системный таймер может выполнять функцию измерения времени
отличного от времени дня. Время дня прекрасно подходит для
определения интервалов времени, измеряемых в секундах или минутах.
Но в некоторых ситуациях, возникающих при управлении
вводом-выводом, нужно определять интервалы времени порядка одной -
двух миллисекунд. Обычно программы отсчитывают такие интервалы с
помощью временного цикла. Программа для такого цикла выглядит
примерно так:
MOV CX, LOOP_VALUE
HERE:LOOP HERE
Вы выбираете константу LOOP_VALUE так, что цикл выполняется в
точности нужное число раз. Это очень хороший метод, если вам нужна
задержка на определенное время. В выше приведенном примере
начальное значение константы LOOP_VALUE, равное 0FFFFH, дает время
выполнения около 250-миллисекунд.
Но предположим, что вы хотите понаблюдать за внешним событием,
и определить, сколко времени займет его наступление. Можно использовать
вариант временного цикла такого, например, вида:
MOV CX, 0
HERE:
; --- проверка возникновения события
IN AL, DX
TEST AL, MASK_BIT
LOOPNE HERE
DONE:
; --- CX содержит число итераций цикла
Таким способом вы считаете число итераций цикла, чтобы
вычислить затраченное на него время. Этот метод предполагает, что
событие возникнет до того, как содержимое регистра CX второй раз
достигнет 0. Но если вам нужно измерить что-то с точностью до
микросекунд, этот метод не удобен, так как каждая итерация цикла
требует от 10 до 20 микросекунд. Системный таймер дает лучшее
решение. Поскольку он изменяет свое значение каждые 840 наносекунд,
вы сможете определить длительность события с точностью до
микросекунды.
На Фиг. 8.5 показан пример программы, вычисляющей время события
с помощью системного таймера. В этом примере в качестве
регистрируемого события используется канал 2 таймера. В первой
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:05:19
Фиг. 8.5 Управление системным таймером Page 1-1
PAGE ,132
TITLE Фиг. 8.5 Управление системным таймером
0000 STACK SEGMENT STACK
0000 0040[ DW 64 DUP (?)
????
]
0080 STACK ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 TIMER PROC FAR
0000 1E PUSH DS ; Занесение адреса возврата
0001 B8 0000 MOV AX, 0
0004 50 PUSH AX
0005 B0 B6 MOV AL, 10110110B ; Выборка таймера 2
0007 E6 43 OUT 43H, AL
0009 B8 0500 MOV AX, 500H
000C E6 42 OUT 42H, AL ; Таймер 2 установлен на 500 отсчетов
000E 8A C4 MOV AL, AH
0010 E6 42 OUT 42H, AL
0012 E8 001D R CALL LOW_TO_HIGH ; Выборка времени первого перехода с 0 на 1
0015 8B D8 MOV BX, AX ; Сохранение значения в регистре BX
0017 E8 001D R CALL LOW_TO_HIGH ; Выборка времени второго перехода с 0 на 1
001A 2B D8 SUB BX, AX ; Вычитая получаем длину цикла
001C CB RET
001D TIMER ENDP
Фиг. 8.5 Управление системным таймером (начало)
;--------------------------------------------
; Эта подпрограмма ждет перехода с нижнего уровня
; сигнала на верхний (с 0 на 1) в таймере 2
; и возвращает в регистре AX значение счетчика таймера 0
;--------------------------------------------
001D LOW_TO_HIGH PROC NEAR
001D E4 62 IN AL, 62H ; Проверка разряда таймера 2
001F A8 20 TEST AL, 20H
0021 75 FA JNZ LOW_TO_HIGH ; Цикл: сигнал таймера 2 на
; нижнем уровне
0023 WAIT_HIGH:
0023 E4 62 IN AL, 62H ; Проверка разряда таймера 2
0025 A8 20 TEST AL, 20H
0027 74 FA JZ WAIT_HIGH ; Цикл: сигнал таймера 2 на
; верхнем уровне
0029 B0 00 MOV AL, 0 ; Послать команду в регистр управления тай-
002B E6 43 OUT 43H, AL ; мером 2, которая "замораживает" таймер 2
002D 90 NOP
002E 90 NOP ; Задержка, необходимая для 8253
002F E4 40 IN AL, 40H ; Чтение младшего байта счетчика
0031 8A E0 MOV AH, AL
0033 90 NOP
0034 E4 40 IN AL, 40H ; Чтение старшего байта счетчика
0036 86 E0 XCHG AH, AL
0038 C3 RET ; Возвращение значения в AX
0039 LOW_TO_HIGH ENDP
0039 CODE ENDS
END
Фиг. 8.5 Системный таймер (продолжение)
части программы этот канал таймера загружается известным значением.
Здесь мы произвольно выбрали число 500H. Обратите внимание, что эта
часть программы идентична способу генерации звуков с помощью втого
канала таймера.
Наша программа вызывает подпрограмму LOW_TO_HIGH, которая
возвращает значение таймера в тот момент, когда на выходе канала 2
таймера отмечается переход от низкого к высокому уровню. Программа
рследит именно за переходом; если бы она регистрировала только
высокий уровень, было бы неизвестно, стал ли сигнал высоким только
что или уже готов стать низким. Подпрограмма посылает нуль в
управляющий регистр таймера (порт 43H), чтобы "заморозить" текущее
значение канала 0. Это позволяет ей прочитать текущее значение
таймера, продолжающего счет. Если бы программа временно не
зафиксировала таймер, она не смогла бы прочитать без ошибки его
16-битовое значение.
Обратим внимание на то, что подпрограмма на Фиг. 8.5 содержит
несколько команд NOP. Эти команды записаны в программе для выдержки
временных соотношений. Если очень внимательно прочитать инструкции
по микросхеме 8253, мы заметим, что между командами IN и OUT,
выполняемыми этой микросхемой, проходит не меньше 1 микросекунды.
Команда NOP занимает как раз достаточно времени, чтобы исключить
нарушение требований микросхемы по времени.
После возврата из подпрограммы программа сохраняет в регистре
BX значение счетчика таймера во время первого перехода с низкого
уровня на высокий. Затем программа снова вызывает подпрограмму,
чтобы зарегистрировать следующий переход с низкого уровня на
высокий на выходе канала 2 таймера. Потом она вычитает одно число
из другого, чтобы определить время цикла канала 2.
Мы уже говорили о том, что загрузка регистра 0 таймера
значением счета 0 - очень полезна. Данная программа подтверждает
это, так как она вычитает два значения таймера, не обращая внимания
на то, какое из них больше, а какое меньше. Так как канал 0 таймера
работает асинхронно по отношению к этой программе, нет никакой
гарантии, что первое читаемое из него число больше второго.
Например, предположим, что первый переход с низкого на высокий
уровень происходит, когда таймер 0 имеет значение 100H. После 500H
циклов значение числа в таймере будет 0FC00H. Счетчик таймера 0
автоматически "проскочил" от значения 0 к значению 0FFFFH, и
значение прочитанное вторым оказалось численно больше первого. Но
из-за того, что регистр таймера снова начинает счет со значения
0FFFFH, мы всегда можем вычитать эти два числа. При этом иногда
будет появляться перенос, иногда нет, но разность этих двух чисел
всегда будет равна числу отсчетов.
Чтобы убедить вас в правильности этого положения, рассмотрим
случай, когда счетчик загружается числом 8000H. Если первый переход
возникает при значении 6000H, второй появится при значении 5B00H, и
разница между ними составляет 500H. Но если первый переход
возникает при значении 100H, второй возникает при значении 7C00H, и
разница станет равна 8500H. Чтобы правильно отреагировать на эту
ситуацию, программа должна была бы проверить, не произошло ли
переполнение счетчика за время отсчета.
При выполнении этой программы вы обнаружите, что значение в
регистре BX составляет около 0A00H, и не равно ожидаемому значению
500H. Так происходит потому, что таймер работает в режиме
уменьшения содержимого счетчика на два по каждому временному
импульсу. Чтобы разобраться в работе микросхемы 8253, нужно
ознакомиться с инструкцией по ее программированию.
Фиг. 8.6 дает сводку для управляющего слова микросхемы 8253.
Для настройки одного из каналов на конкретный режим работы вы
выводите это управляющее слово в порт 43H. Мы уже встречались с
выводом некотрых значений в порт 43H. Чтобы "заморозить" счетчик,
мы послали в этот порт нуль, а для настройки генератора тональности
- код 0B6H. Посмотрим, откуда берутся эти значения.
Два старших бита управляющего слова определяют канал таймера.
Следующие два бита - выполняемую операцию. Когда мы выводим
значение 0, выбирается таймер 0 и запирание данных в счетчике.
Следующие 3 бита задают режим работы выбранного таймера. Эти биты
не играют роли, когда счетчик заперт, но нужны при инициализации
таймера. Оставшийся бит определяет, будет ли счетчик работать как
16-битовое двоичное число или как четырехзначное десятичное в
двоичном представлении.
A
Формат управляющего слова
D7 D6 D5 D4 D3 D2 D1 D0
ЪДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДї
і SC1і SC0і RL1і RL0і M2 і M1 і M0 і BCDі
АДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДЩ
Определение управления
SC - выбор счетчика:
SC1 SC0
ЪДДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДї
і 0 і 0 і задать счетчик 0 і
ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 0 і 1 і задать счетчик 1 і
ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 0 і задать счетчик 2 і
ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 1 і неопределено і
АДДДДДДДДДБДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДЩ
RL - чтение/загрузка:
RL1 RL0
ЪДДДДВДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і 0 і 0 і Операция запирания счетчика (см. і
і і і раздел процедуры READ/WRITE) і
ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 0 і Считать/загрузить старший байт і
ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 0 і 1 і Считать/загрузить младший байт і
ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і 1 і Считать/загрузить младший байт, і
і і і затем - старший і
АДДДДБДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
M - режим:
M2 M1 M0
ЪДДДВДДДВДДДВДДДДДДДДДДДї
і 0 і 0 і 0 і Режим 0 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і 0 і 0 і 0 і Режим 1 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і X і 1 і 0 і Режим 2 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і X і 1 і 1 і Режим 3 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і 1 і 0 і 0 і Режим 4 і
ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
і 1 і 0 і 1 і Режим 5 і
АДДДБДДДБДДДБДДДДДДДДДДДЩ
BCD:
ЪДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
і 0 і 16-битовый двоичный счетчик і
ГДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
і 1 і десятичный в двоичном представлении і
і і (BCD) счетчик (4-х разрядный) і
АДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
Фиг. 8.6 Программирование таймера/счетчика
(с разрешения фирмы Intel; приоритет Intel 1981г.)A
Управляющий код 0B6H, который использовался для генерации
тональности, можно расписать таким образом:
0B6H = 10110110B = 01 11 011 0
Это управляющее слово выбирает канал 2. Счетчик этого канала
работает с 16-битовым значением, младший байт которого загружается
в счетчик первым. Последный бит показывает, что счет будет
двоичным. Можно видеть также, что выбирается третий режим работы.
Микросхема 8253 может работать в шести разных режимах. Но для
наших целей, в конфигурации системы IBM, удобны только два из них.
Режим 3 устанавливается по умолчанию для всех трех каналов таймера.
Это режим работы генератора прямоугольных импульсов: половину
периода выход канала дает нижний уровень, а другую половину периода
- верхний. Счетчик работает в этом режиме, вычитая из своего
значения по два, а не по одному. Во время первого обратного счета
выход низкий, а затем, во время второго счета - высокий. Так как
счетчик считает по два, выход имеет высокий уровень точно половину
заданного времени, и низкий тоже точно половину. Из-за того, что
канал 0 таймера обычно работает в режиме 3, пример на Фиг. 8.5
заканчивает счет со значением счетчика 0A00H, а не 500H, как
ожидалось. Каждый отсчет таймера уменьшает содержимое счетчика на
два.
Так как счетчик считает двойками, он переполняется каждые 27
микросекунд. Если вы хотите измерять события более длительные, то
вам придется воспользоваться другим способом измерения времени.
Другой режим таймера, который используется в IBM PC - это режим
0. Этот режим называют прерыванием по завершению счета. В этом
режиме таймер не работает непрерывно. После его установки таймер до
тех пор не начинает считать (единицами), пока в него полностью не
будет загружено число. Затем счетчик считает в сторону уменьшения с
частотой синхроимпульсов, пока не достигнет нуля. В этот момент его
выход становится высоким. Поскольку выход канала 0 таймера
подключен к прерыванию 0 контроллера 8259, в системе возникает
прерывание.
Режим прерывания по завершению счета полезен, если вы хотите в
определенный момент подать программе сигнал с помощью прерывания.
Так как счетчик ограничен шестнадцатью битами, максимальный
отсчитываемый интервал времени составляет 55 миллисекунд. Если этот
интервал слишком мал, нужен другой метод измерения времени.
Если вы хотите измерять интервал времени в секундах, нужно
оставить таймер в его обычном режиме работы. Система BIOS позволяет
захватывать управление системой каждые 55 миллисекунд, и в каждый
такой момент вы можете решить, не исчерпался ли нужный промежуток
времени.
Если время нужной вам задержки находится между 55
миллисекундами и 5 секундами, можно использовать метод без
использования программ BIOS. Например, вам хочется сделать задержку
на 150 миллисекунд. Используя режим прерывания по завершению счета,
вы настраиваете таймер на прерывание через 50 миллисекунд (этому
соответствует значенние счетчика около 59500). Обработчик
прерывания программируется так, чтобы, получая управление первые
два раза, он заново устанавливал таймер на 50 миллисекунд. По
третьему прерыванию от таймера, когда 150 миллисекунд исчерпаны,
можно предпринять нужные действия.
При организации задержек через таймер всегда нужна некоторая
осторожность. Как упоминалось выше, канал 1 таймера выполняет одну
важную аппаратную функцию. Если вы модифицируете число в канале 1,
ваша программа может немедленно разрушиться. Использование канала 2
таймера безопасно. Этот канал подключен только к динамику и выходу
кассетного магнитофона. Отсюда, очевидно, следует, что нельзя
использовать канал 2 таймера для отсчета промежутков времени в одно
время с попытками воспроизводить мелодии через динамик. И наконец,
BIOS пользуется услугами таймера 0 для различных системных функций.
При обсуждении BIOS будет видно, что прерывание по времени суток
управляет не только текущим временем, но также обслуживает и
двигатель накопителя на дискетах. Перед тем, как изменять настройку
канала 0 таймера для любых целей, нужно понять, какие существующие
функции вы можете при этом изменить.