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

4.7. Другие устройства

4.7.1. Системный таймер

Начиная с IBM AT, персональные компьютеры содержат два устройства для управления процессами — часы реального времени (RTC) и собственно системный таймер. Часы реального времени получают питание от аккумулятора на материнской плате и работают даже тогда, когда компьютер выключен. Это устройство можно использовать для определения/установки текущих даты и времени, установки будильника с целью выполнения каких-либо действий и для вызова прерывания IRQ8 (INT 4Ah) каждую миллисекунду. Системный таймер используется одновременно для управления контроллером прямого доступа к памяти, для управления динамиком и как генератор импульсов, вызывающий прерывание IRQ0 (INT 8h) 18,2 раза в секунду. Таймер предоставляет богатые возможности для препрограммирования на уровне портов ввода-вывода, но на уровне DOS и BIOS и часы реального времени, и системный таймер используются только как средство определения/установки текущего времени и организации задержек.


Функция DOS 2Ah — Определить дату

Ввод: AX = 2Ah
Вывод: СХ = год (1980 – 2099)
DH = месяц
DL = день
AL = день недели (0 — воскресенье, 1 — понедельник...)

Функция DOS 2Ch — Определить время

Ввод: AX = 2Ch
Вывод: СН = час
CL = минута
DH = секунда
DL = сотая доля секунды

Эта функция использует системный таймер, так что время изменяется только 18,2 раза в секунду и число в DL увеличивается сразу на 5 или 6.


Функция DOS 2Bh — Установить дату

Ввод: АН = 2Bh
СХ = год (1980 – 2099)
DH = месяц
DL = день
Вывод: АН = FFh, если введена несуществующая дата,
АН = 00h, если дата установлена

Функция DOS 2Dh — Установить время

Ввод: АН = 2Dh
СН = час
CL = минута
DH = секунда
DL = сотая доля секунды
Вывод: AL = FFh, если введено несуществующее время,
AL = 00h, если время установлено

Функции 2Bh и 2Dh устанавливают одновременно как внутренние часы DOS, которые управляются системным таймером и обновляются 18,2 раза в секунду, так и часы реального времени. BIOS позволяет управлять часами напрямую:


INT 1Ah АН = 04h — Определить дату RTC

Ввод: АН = 04h
Вывод: CF = 0, если дата прочитана
СХ = год (в формате BCD, то есть 1998h для 1998-го года)
DH = месяц (в формате BCD)
DL = день (в формате BCD)
CF = 1, если часы не работают или попытка чтения пришлась на момент обновления

INT 1Ah АН = 02h — Определить время RTC

Ввод: АН = 02h
Вывод: CF = 0, если время прочитано
СН = час (в формате BCD)
CL = минута (в формате BCD)
DH = секунда (в формате BCD)
DL = 01h, если действует летнее время, 00h, если нет
CF = 1, если часы не работают или попытка чтения пришлась на момент обновления

INT 1Ah АН = 05h — Установить дату RTC

Ввод: АН = 05h
СХ = год (в формате BCD)
DH = месяц
DL = день

INT 1Ah АН = 03h — Установить время RTC

Ввод: АН = 03h
СН = час (в формате BCD)
CL = минута (в формате BCD)
DH = секунда (в формате BCD)
DL = 01h, если используется летнее время, 0 — если нет

Кроме того, BIOS позволяет использовать RTC для организации будильников и задержек:


INT 1Ah АН = 06h — Установить будильник

Ввод: АН = 06h
СН = час (BCD)
CL = минута (BCD)
DH = секунда (BCD)
Вывод: CF = 1, если произошла ошибка (будильник уже установлен или прерывание вызвано в момент обновления часов)
CF = 0, если будильник установлен

Теперь каждые 24 часа, когда время совпадет с заданным, часы реального времени вызовут прерывание IRQ8 (INT 4Ah), которое должна обрабатывать установившая будильник программа. Если при вызове СН = FFh, CL*nbsp;= FFh, a DH = 00h, то будильник начнет срабатывать раз в минуту.


INT 1Ah АН = 07 — Отменить будильник

Ввод: АН = 07h

Эта функция позволяет отменить будильник, например для того, чтобы установить его на другое время.

BIOS отслеживает каждый отсчет системного таймера с помощью своего обработчика прерывания IRQ0 (INT 8h) и увеличивает на 1 значение 32-битного счетчика, который располагается в памяти по адресу 0000h:046Ch, причем при переполнении этого счетчика байт по адресу 0000h:0470h увеличивается на 1.


INT 1Ah АН = 00h — Считать значение счетчика времени

Ввод: АН = 00h
Вывод: CX:DX = значение счетчика
AL = байт переполнения счетчика

INT 1Ah АН = 01h — Изменить значение счетчика времени

Ввод: АН = 01h
CX:DX = значение счетчика

Программа может считывать значение этого счетчика в цикле (через прерывание или просто командой MOV) и организовывать задержки, например пока счетчик не увеличится на 1. Но так как этот счетчик использует системный таймер, минимальная задержка будет равна приблизительно 55 микросекундам. Частоту таймера можно изменить, программируя его на уровне портов, но BIOS предоставляет для этого специальные функции.


INT 15h АН = 86h — Формирование задержки

Ввод: АН = 86h
CX:DX = длительность задержки в микросекундах (миллионных долях секунды!)
Вывод: AL = маска, записанная обработчиком в регистр управления прерываниями
CF = 0, если задержка выполнена
CF = 1, если таймер был занят

Если нужно запустить счетчик времени и продолжить выполнение программы, можно воспользоваться еще одной функцией.


INT 15h АН = 83h — Запуск счетчика времени

Ввод: АН = 83h
AL = 0 — запустить счетчик
AL = 1 — прервать счетчик
CX:DX = длительность задержки в микросекундах
ES:BX = адрес байта, старший бит которого по окончании работы счетчика будет установлен в 1
Вывод: AL = маска, записанная обработчиком в регистр управления прерываниями
CF = 0, если задержка выполнена
CF = 1, если таймер был занят

Минимальный интервал для этих функций на большинстве систем обычно составляет около 1000 микросекунд. Воспользуемся функцией организации задержки для небольшой интерактивной игры:

; worm.asm
; Игра "Питон" (или "Змея", или "Червяк"). Управление осуществляется клавишами
; управления курсором, питон погибает, если он выходит за верхнюю или нижнюю
; границу экрана или самопересекается.
        .model     tiny
        .code
        .186                     ; для команды push 0A000h
        org        100h          ; СОМ-файл
start:
        mov        ax,cs         ; текущий сегментный адрес плюс
        add        ax,1000h      ; 1000h = следующий сегмент,
        mov        ds,ax         ; который будет использоваться
                                 ; для адресов головы и хвоста
        push       0A000h        ; 0A000h - сегментный адрес
        pop        es            ; видеопамяти (в ES)
        mov        ax,13h        ; графический режим 13h
        int        10h
        mov        di,320*200
        mov        cx,600h       ; заполнить часть видеопамяти,
                                 ; остающуюся за пределами
        rep        stosb         ; экрана, ненулевыми значениями
                                 ; (чтобы питон не смог выйти
                                 ; за пределы экрана)
        xor        si,si         ; начальный адрес хвоста в DS:SI
        mov        bp,10         ; начальная длина питона - 10
        jmp        init_food     ; создать первую еду
main_cycle:
; использование регистров в этой программе:
; АХ - различное
; ВХ - адрес головы, хвоста или еды на экране
; СХ - 0 (старшее слово числа микросекунд для функции задержки)
; DX - не используется (модифицируется процедурой random)
; DS - сегмент данных программы (следующий после сегмента кода)
; ES - видеопамять
; DS:DI - адрес головы
; DS:SI - адрес хвоста
; ВР - добавочная длина (питон растет, пока ВР > 0, ВР уменьшается
; на каждом шаге, пока не станет нулем)

        mov        dx,20000            ; пауза - 20 000 микросекунд
        mov        ah,86h              ; (СХ = 0 после REP STOSB
                                       ; и больше не меняется)
        int        15h                 ; задержка
        mov        ah,1                ; проверка состояния клавиатуры
        int        16h
        jz         short no_keypress   ; если клавиша не нажата -
        xor        ah,ah               ; АН = 0 - считать скан-код
        int        16h                 ; нажатой клавиши в АН,
        cmp        ah,48h              ; если это стрелка вверх,
        jne        short not_up
        mov        word ptr cs:move_direction,-320 ; изменить
                                       ; направление движения на "вверх",
not_up:
        cmp        ah,50h              ; если это стрелка вниз,
        jne        short not_down
        mov        word ptr cs:move_direction,320  ; изменить
                                       ; направление движения на "вниз",
not_down:
        cmp        ah,4Bh              ; если это стрелка влево,
        jne        short not_left
        mov        word ptr cs:move_direction,-1   ; изменить
                                       ; направление движения на "влево",
not_left:
        cmp        ah,4Dh              ; если это стрелка вправо,
        jne        short no_keypress
        mov        word ptr cs:move_direction,1    ; изменить
                                       ; направление движения на "вправо",
no_keypress:
        and        bp,bp               ; если питон растет (ВР > 0),
        jnz        short advance_head  ; пропустить стирание хвоста,
        lodsw                          ; иначе: считать адрес хвоста из
                                       ; DS:SI в АХ и увеличить SI на 2
        xchg       bx,ax
        mov        byte ptr es:[bx],0  ; стереть хвост на экране,
        mov        bx,ax
        inc        bp                  ; увеличить ВР, чтобы следующая
                                       ; команда вернула его в 0,
advance_head:
        dec        bp                  ; уменьшить ВР, так как питон
; вырос на 1, если стирание хвоста было пропущено,
; или чтобы вернуть его в 0 - в другом случае
        add        bx,word ptr cs:move_direction
; bx = следующая координата головы
        mov        al,es:[bx]          ; проверить содержимое экрана в точке
                                       ; с этой координатой,
        and        al,al               ; если там ничего нет,
        jz         short move_worm     ; передвинуть голову,
        cmp        al,0Dh              ; если там еда,
        je         short grow_worm     ; увеличить длину питона,
        mov        ax,3                ; иначе - питон умер,
        int        10h                 ; перейти в текстовый режим
        retn                           ; и завершить программу

move_worm:
        mov        [di],bx             ; поместить адрес головы в DS:DI
        inc        di
        inc        di                  ; и увеличить DI на 2,
        mov        byte ptr es:[bx],09 ; вывести точку на экран,
        cmp        byte ptr cs:eaten_food,1 ; если предыдущим
                                       ; ходом была съедена еда,
        je         if_eaten_food       ; создать новую еду,
        jmp        short main_cycle    ; иначе - продолжить основной цикл

grow_worm:
        push       bx                  ; сохранить адрес головы
        mov        bx,word ptr cs:food_at ; bx - адрес еды
        xor        ах,ах               ; АХ = 0
        call       draw_food           ; стереть еду
        call       random              ; AX - случайное число
        and        ax,3Fh              ; AX - случайное число от 0 до 63
        mov        bp,ax               ; это число будет добавкой
                                       ; к длине питона
        mov        byte ptr cs:eaten_food,1 ; установить флаг
                                       ; для генерации еды на следующем ходе
        pop        bx                  ; восстановить адрес головы ВХ
        jmp        short move_worm     ; перейти к движению питона

if_eaten_food:                         ; переход сюда, если еда была съедена
        mov        byte ptr cs:eaten_food,0 ; восстановить флаг
init_food:                             ; переход сюда в самом начале
        push       bx                  ; сохранить адрес головы
make_food:
        call       random              ; AX - случайное число
        and        ax,0FFFEh           ; AX - случайное четное число
        mov        bx,ax               ; BX - новый адрес для еды
        xor        ах,ах
        cmp        word ptr es:[bx],ax ; если по этому адресу
                                       ; находится тело питона,
        jne        make_food           ; еще раз сгенерировать случайный адрес,
        cmp        word ptr es:[bx+320],ax ; если на строку ниже
                                       ; находится тело питона -
        jne        make_food           ; то же самое,
        mov        word ptr cs:food_at,bx ; поместить новый адрес
                                       ; еды в food_at,
        mov        ax,0D0Dh            ; цвет еды в АХ
        call       draw_food           ; нарисовать еду на экране
        pop        bx
        jmp        main_cycle

; процедура draw_food
; изображает четыре точки на экране - две по адресу ВХ и две на следующей
; строке. Цвет первой точки из пары - AL, второй - АН

draw_food:
        mov        es:[bx],ax
        mov        word ptr es:[bx+320],ax
        retn

; генерация случайного числа
; возвращает число в АХ, модифицирует DX

random: mov        ах,word ptr cs:seed,
        mov        dx,8E45h
        mul        dx
        inc        ax
        mov        cs:word ptr seed,ax
        retn

;  переменные

eaten_food         db            0
move_direction     dw            1   ; направление движения: 1 - вправо,
                                     ; -1 - влево, 320 - вниз, -320 - вверх
seed:                                ; это число хранится за концом программы,
food_at equ        seed+2            ; а это - за предыдущим
        end        start

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