Assembler - язык неограниченных возможностей
0e1cc9b4

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


Начиная с 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


Содержание раздела