[Назад] [Далее]

5.10.5. Таймер

Все, что нам было известно до сих пор о системном таймере, — это устройство, вызывающее прерывание IRQ0 приблизительно 18,2 раза в секунду. На самом деле программируемый интервальный таймер — весьма сложная система, включающая в себя целых три устройства — три канала таймера, каждый из которых можно запрограммировать для работы в одном из шести режимов. И более того, на большинстве современных материнских плат располагаются два таких таймера, так что число каналов оказывается равным шести. Для своих нужд программы могут использовать канал 2 (если им не нужен динамик) и канал 4 (если присутствует второй таймер). При необходимости можно перепрограммировать и канал 0, но затем надо будет вернуть его в исходное состояние, чтобы BIOS и DOS могли продолжать работу.

В пространстве портов ввода-вывода для таймера выделена область от 40h до 5Fh:

Все управление таймером осуществляется путем вывода одного байта в порт 43h (для первого таймера). Рассмотрим назначение бит в этом байте.

Если биты 7 – 6 равны 11, считается, что байт, посылаемый в порт 43h, — команда чтения счетчиков, формат которой отличается от команды программирования канала:

Если этой командой запрашивается состояние каналов, новые команды будут игнорироваться, пока не прочтется состояние из всех каналов, которые были заказаны битами 3 – 1.

Состояние и значение счетчика данного канала получают чтением из порта, соответствующего требуемому каналу. Формат байта состояния имеет следующий вид:

Для того чтобы запрограммировать таймер в режиме 3, в котором работают каналы 0 и 2 по умолчанию и который чаще всего применяют в программах, требуется выполнить следующие действия:

  1. Вывести в регистр 43h команду (для канала 0) 0011011h, то есть установить режим 3 для канала 0, и при чтении/записи будет пересылаться сначала младшее слово, а потом старшее.
  2. Послать младший байт начального значения счетчика в порт, соответствующий выбранному каналу (42h для канала 2).
  3. Послать старший байт начального значения счетчика в этот же порт.

После этого таймер немедленно начнет уменьшать введенное число от начального значения к нулю со скоростью 1 193 180 раз в секунду (четверть скорости процессора 8088). Каждый раз, когда это число достигает нуля, оно снова возвращается к начальному значению. Кроме того, при достижении счетчиком нуля таймер выполняет соответствующую функцию — канал 0 вызывает прерывание IRQO, а канал 2, если включен динамик, посылает ему начало следующей прямоугольной волны, заставляя его работать на установленной частоте. Начальное значение счетчика для канала 0 по умолчанию составляет 0FFFFh (65 535), то есть максимально возможное. Поэтому точная частота вызова прерывания IRQ0 равна 1 193 180/65 536 = 18,20648 раза в секунду.

Чтобы прочитать текущее значение счетчика, надо:

  1. Послать в порт 43h команду фиксации значения счетчика для выбранного канала (биты 5 – 4 равны 00h).
  2. Послать в порт 43h команду перепрограммирования канала без изменения режима его работы, если нужно изменить способ чтения/записи (обычно не требуется).
  3. Прочитать из порта, соответствующего выбранному каналу, младший байт зафиксированного значения счетчика.
  4. Прочитать из того же порта старший байт.

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

Посмотрим в качестве примера, как при помощи таймера измерить, сколько времени проходит между реальным аппаратным прерыванием и моментом, когда обработчик этого прерывания получает управление (почему это важно, см. пример программ вывода звука из глав 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 представляет собой идеальный вариант такого начального числа для большинства приложений.


п»ї
"target=_blank><\/a>") //-->