Таймер
Все, что нам было известно до сих пор о системном таймере, — это устройство, вызывающее прерывание IRQ0 приблизительно 18,2 раза в секунду. На самом деле программируемый интервальный таймер — весьма сложная система, включающая в себя целых три устройства — три канала таймера, каждый из которых можно запрограммировать для работы в одном из шести режимов. И более того, на большинстве современных материнских плат располагаются два таких таймера, так что число каналов оказывается равным шести. Для своих нужд программы могут использовать канал 2 (если им не нужен динамик) и канал 4 (если присутствует второй таймер). При необходимости можно перепрограммировать и канал 0, но затем надо будет вернуть его в исходное состояние, чтобы BIOS и DOS могли продолжать работу.
В пространстве портов ввода-вывода для таймера выделена область от 40h до 5Fh:
порт 40h — канал 0 (генерирует IRQ0)
порт 41h — канал 1 (поддерживает обновление памяти)
порт 42h — канал 2 (управляет динамиком)
порт 43h — управляющий регистр первого таймера
порты 44h – 47h — второй таймер компьютеров с шиной MicroChannel
порты 48h – 4Bh — второй таймер компьютеров с шиной EISA
Все управление таймером осуществляется путем вывода одного байта в порт 43h (для первого таймера). Рассмотрим назначение бит в этом байте.
биты 7 – 6: если не 11 — это номер канала, который будет программироваться
00,01,10 = канал 0,1,2
биты 5 – 4:
00 — зафиксировать текущее значение счетчика для чтения (в этом случае биты 3 – 0 не используются)
01 — чтение/запись только младшего байта
10 — чтение/запись только старшего байта
11 — чтение/запись сначала младшего, а потом старшего байта
биты 3 – 1: режим работы канала
000: прерывание IRQ0 при достижении нуля
001: ждущий мультивибратор
010: генератор импульсов
011: генератор прямоугольных импульсов (основной режим)
100: программно запускаемый одновибратор
101: аппаратно запускаемый одновибратор
бит 0: формат счетчика:
0 — двоичное 16-битное число (0000 – FFFFh)
1 — двоично-десятичное число (0000 – 9999)
Если биты 7 – 6 равны 11, считается, что байт, посылаемый в порт 43h, — команда чтения счетчиков, формат которой отличается от команды программирования канала:
биты 7 – 6: 11 (код команды чтения счетчиков)
биты 5 – 4: режим чтения:
00: сначала состояние канала/потом значение счетчика
01: значение счетчика
10: состояние канала
биты 3 – 1: команда относится к каналам 3 – 1
Если этой командой запрашивается состояние каналов, новые команды будут игнорироваться, пока не прочтется состояние из всех каналов, которые были заказаны битами 3 – 1.
Состояние и значение счетчика данного канала получают чтением из порта, соответствующего требуемому каналу. Формат байта состояния имеет следующий вид:
бит 7: состояние входа OUTx на момент выполнения команды чтения счетчиков. Так как в режиме 3 счетчик уменьшается на 2 за каждый цикл, состояние этого бита, замороженное командой фиксации текущего значения счетчика, укажет, в каком полуцикле находился таймер
бит 6: 1/0 — состояние счетчика не загружено/загружено (используется в режимах 1 и 5, а также после команды фиксации текущего значения)
биты 5 – 0: совпадают с битами 5 – 0 последней команды, посланной в порт 43h
Для того чтобы запрограммировать таймер в режиме 3, в котором работают каналы 0 и 2 по умолчанию и который чаще всего применяют в программах, требуется выполнить следующие действия:
Вывести в регистр 43h команду ( для канала 0) 0011011h, то есть установить режим 3 для канала 0, и при чтении/записи будет пересылаться сначала младшее слово, а потом старшее.
Послать младший байт начального значения счетчика в порт, соответствующий выбранному каналу (42h для канала 2).
Послать старший байт начального значения счетчика в этот же порт.
После этого таймер немедленно начнет уменьшать введенное число от начального значения к нулю со скоростью 1 193 180 раз в секунду (четверть скорости процессора 8088). Каждый раз, когда это число достигает нуля, оно снова возвращается к начальному значению. Кроме того, при достижении счетчиком нуля таймер выполняет соответствующую функцию — канал 0 вызывает прерывание IRQO, а канал 2, если включен динамик, посылает ему начало следующей прямоугольной волны, заставляя его работать на установленной частоте. Начальное значение счетчика для канала 0 по умолчанию составляет 0FFFFh (65 535), то есть максимально возможное. Поэтому точная частота вызова прерывания IRQ0 равна 1 193 180/65 536 = 18,20648 раза в секунду.
Чтобы прочитать текущее значение счетчика, надо:
Послать в порт 43h команду фиксации значения счетчика для выбранного канала (биты 5 – 4 равны 00h).
Послать в порт 43h команду перепрограммирования канала без изменения режима его работы, если нужно изменить способ чтения/записи (обычно не требуется).
Прочитать из порта, соответствующего выбранному каналу, младший байт зафиксированного значения счетчика.
Прочитать из того же порта старший байт.
Для таймера найдется много применений, единственное ограничение здесь: таймер — это глобальный ресурс, и перепрограммировать его в многозадачных системах можно только с ведома операционной системы, если она вообще это позволяет.
Посмотрим в качестве примера, как при помощи таймера измерить, сколько времени проходит между реальным аппаратным прерыванием и моментом, когда обработчик этого прерывания получает управление (почему это важно, см. пример программ вывода звука из глав 5.10.8 и 5.10.9). Так как IRQ0 происходит при нулевом значении счетчика, нам достаточно прочитать его значение при старте обработчика и обратить его знак (потому что счетчик таймера постоянно уменьшается).
; latency.asm ; измеряет среднее время, проходящее между аппаратным прерыванием и запуском ; соответствующего обработчика. Выводит среднее время в микросекундах после ; нажатия любой клавиши (на самом деле в 1/1 193 180). ; Программа использует 16-битный сумматор для простоты, так что может давать ; неверные результаты, если подождать больше нескольких минут
.model tiny .code .386 ; для команды shld org 100h ; COM-программа start: mov ax,3508h ; AH = 35h, AL = номер прерывания int 21h ; получить адрес обработчика mov word ptr old_int08h,bx ; и записать его в old_int08h mov word ptr old_int08h+2,es mov ax,2508h ; AH = 25h, AL = номер прерывания mov dx,offset int08h_handler ; DS:DX - адрес обработчика int 21h ; установить обработчик ; с этого момента в переменной latency накапливается сумма mov ah,0 int 16h ; пауза до нажатия любой клавиши mov ax,word ptr latency ; сумма в АХ cmp word ptr counter,0 ; если клавишу нажали немедленно, jz dont_divide ; избежать деления на ноль xor dx,dx ; DX = 0 div word ptr counter ; разделить сумму на число накоплений dont_divide: call print_ax ; и вывести на экран
mov ax,2508h ; АН = 25h, AL = номер прерывания lds dx,dword ptr old_int08h ; DS:DX = адрес обработчика int 21h ; восстановить старый обработчик ret ; конец программы
latency dw 0 ; сумма задержек counter dw 0 ; число вызовов прерывания
; Обработчик прерывания 08h (IRQ0) ; определяет время, прошедшее с момента срабатывания IRQ0 int08h_handler proc far push ax ; сохранить используемый регистр mov al,0 ; фиксация значения счетчика в канале 0 out 43h,al ; порт 43h: управляющий регистр таймера ; так как этот канал инициализируется BIOS для 16-битного чтения/записи, другие ; команды не требуются in al,40h ; младший байт счетчика mov ah,al ; в АН in al,40h ; старший байт счетчика в AL xchg ah,al ; поменять их местами neg ax ; обратить его знак, так как счетчик ; уменьшается add word ptr cs:latency,ax ; добавить к сумме inc word ptr cs:counter ; увеличить счетчик накоплений pop ax db 0EAh ; команда jmp far old_int08h dd 0 ; адрес старого обработчика int08h_handler endp
; процедура print_ax ; выводит АХ на экран в шестнадцатеричном формате print_ax proc near xchg dx,ax ; DX = AX mov cx,4 ; число цифр для вывода shift_ax: shld ax,dx,4 ; получить в AL очередную цифру rol dx,4 ; удалить ее из DX and al,0Fh ; оставить в AL только эту цифру cmp al,0Ah ; три команды, переводящие sbb al,69h ; шестнадцатеричную цифру в AL das ; в соответствующий ASCII-код int 29h ; вывод на экран loop shift_ax ; повторить для всех цифр ret print_ax endp end start
Таймер можно использовать для управления динамиком, для точных измерений отрезков времени, для создания задержек, для управления переключением процессов и даже для выбора случайного числа с целью запуска генератора случайных чисел — текущее значение счетчика канала 0 представляет собой идеальный вариант такого начального числа для большинства приложений.
Содержание раздела