Иллюстрированный самоучитель по Assembler

       

Чтение и обработка показаний часов реального времени


.586   ;Будут использоваться дополнительные команды

assume CS:code,ds:data 

code segment use 16

main proc

mov AX,data      ;Настроим DS наш

mov DS,Ax            ;сегмент данных

;Сохраним исходный вектор 4Ah

mov AX,354Ah

int 21h

mov word ptr old_4a,BX

mov word ptr old_4a+2,ES

;Установим наш обработчик прерываний 4Ah

mov AX,254Ah

push DS                            ;Сохраним DS

push CS                            ;Настроим DS на сегмент 

pop DS                             ;команд

mov DX,offset new_4a: DS:DX->new_4a

int 21h

pop DS                             ;Восстановим DS

;Установим будильник

movAH,02h                      ;Чтение текущего времени

int 1Ah

call add_time                   ;Прибавим 1 секунду

mov AH,06h                     ;Установим будильник на это время

int 1Ah

;Остановим программу, чтобы наблюдать прерывания

mov AH,01h                  ;Функция ввода с клавиатуры

int 21h

;Завершим программу, прибрав за собой

mov AH,07h                 ;Сброс будильника


int 1Ah
Ids  DX,old_4a/DS:DX=исходный вектор
mov AX,254Ah       ;Установим исходный вектор
int 21h
mov AX,4C00h       ;Завершим программу
int 21h
main endp
; Наш обработчик прерывания от будильника new_4a proc
push a                        ;Сохраним все регистры
push DS                    ;Сохраним еще и
push ES                    ;сегментные регистры
mov AX ,seg hour     ;Настроим DS на наш
mov DX,AX                ;сегмент данных
mov AH,02h               ;Прочитаем текущее время
int 1Ah                        ;из часов реального времени
push CX                     ;Сохраним полученное
push DX                     ;текущее время
В примере 3-9 используются несколько команд, отсутствующих в МП 86: команды сохранения в стеке и восстановления всех регистров общего назначения pusha и рора, а также команда сдвига shl с числовым операндом. Для того, чтобы эти команды распознавались ассемблером, в программу включена директива .586 (можно было бы обойтись и директивой .386). В этом случае необходимо оба сегмента объявить с описателем use16.

Программа состоит из главной процедуры main, процедуры new_4a обработчика прерываний от будильника, а также трех вспомогательных процедур-подпрограмм add_time, add_unit и conv. Главная процедура сохраняет исходный вектор прерывания 4Ah, устанавливает новый обработчик этого прерывания, читает текущее время и устанавливает будильник на время, отстоящее от текущего на 1 секунду, а затем останавливается в ожидании нажатия любой клавиши. Пока программа стоит, обрабатываются прерывания от будильника и в правый верхний угол экрана каждую секунду выводится текущее время. После нажатия любой клавиши программа завершается, предварительно сбросив будильник и восстановив исходное содержимое вектора 4Ah.


Легко видеть, что в предложенном варианте программа имеет мало практического смысла, так как она не выполняет, кроме вывода времени, никакой полезной работы. В то же время, пока эта программа не завершилась, запустить другую программу нельзя, так как DOS является однозадачной системой. Если, однако, написать нашу программу в формате .СОМ и сделать ее резидентной, мы получим возможность запускать любые программы и одновременно наблюдать на экране текущее время. Такого средства в DOS нет, и в какой-то ситуации оно может оказаться полезным. Методика разработки резидентных программ описана выше; читатель может выполнить необходимые преобразования самостоятельно.

Рассмотрим теперь программу обработчика прерываний будильника. Прежде всего в нем командой pusha (push all, сохранить все) сохраняются все регистры общего назначения и, кроме того, два сегментных регистра DS и ES, которые будут использоваться в обработчике. Далее регистр DS настраивается на сегментный адрес того сегмента, в который входит ячейка hour, т.е. фактически на наш сегмент команд. На первый взгляд это действие может показаться бессмысленным. Ведь в начале процедуры main в регистр DS уже был помещен адрес нашего сегмента данных data. Зачем же эту операцию повторять? Дело в том, что процедура new_4a, будучи формально обработчиком программного прерывания 4Ah, фактически представляет собой обработчик аппаратного прерывания от часов реального времени, которое, как и любое аппаратное прерывание, может придти в любой момент времени. В принципе прерываемая программа в этот момент может выполнять любые действия, и содержимое регистра DS может быть любым. Если же говорить о нашей программе, то она находится в цикле ожидания нажатия клавиши. Этот цикл организует функция 01h DOS, которая, между прочим, время от времени обращается к своему драйверу клавиатуры, а тот - к программам BIOS ввода символа с клавиатуры. Вполне вероятно (а на самом деле так оно и есть), что при выполнении упомянутых операций используется регистр DS, который в этом случае указывает уже не на наш сегмент данных, а на различные системные области. Другими словами, при входе в обработчик прерывания содержимое регистра DS неизвестно, и его следует инициализировать заново, обязательно сохранив исходное значение. Если перед выходом из обработчика это исходное значение не восстановить, будет неминуемо разрушена DOS.


Сохранив регистры и настроив DS, мы вызываем функцию 02h прерывания lAh чтения текущего времени. Время возвращается, как уже говорилось, в упакованном двоично-десятичном формате (по две цифры в байте) в регистрах СН (часы), CL (минуты) и DH (секунды). Нам это время понадобится еще раз в конце обработчика для установки будильника заново, и чтобы второй раз не вызывать функцию 02h, полученное время (т.е. содержимое регистров СХ и DX) сохраняется в стеке.

Далее выполняется последовательное преобразование BCD-цифр, составляющих время, в коды ASCII соответствующих символов. Число часов (две упакованные BCD-цифры) переносится в регистр AL, и вызывается подпрограмма conv, которая преобразует старшую цифру часов в код ASCII и возвращает его в регистре АН. Этот код помещается в объявленную в сегменте данных строку-шаблон hour, в которой заготовлены пустые пока места для символов цифр, составляющих время, а также имеются разделительные двоеточия. Для удобства обращения к элементам этой строки, она разделена на части и каждая часть снабжена собственным именем - min для поля минут и sec для поля секунд.

Подпрограмма conv преобразования BCD-цифры в код ASCII состоит всего из трех предложений, не считая заключительной команды ret. Двух разрядное BCD-число передается в подпрограмму в регистре AL. После обнуления регистра АН, который будет служить приемником для образования конечного результата, содержимое AL сдвигается командой shl влево на 4 бит, в результате чего старший полубайт регистра AL, т.е. старшая цифра числа, перемещается в регистр АН (рис. 3.9). Двоично-десятичная цифра представляет собой просто двоичное представление цифры; прибавление к ее коду кода символа "0" (числа 30h) дает код ASCII этой цифры.

Мы преобразовали пока только старший полубайт регистра СН. Для выделения младшего полубайта на регистр СН накладывается маска 0Fh,
Чтение и обработка показаний часов реального времени

Рис. 3.9. Алгоритм работы подпрограммы conv.
которая обнуляет старший полубайт, не затрагивая младшего. Прибавление кода ASCII нуля к коду десятичной цифры образует код ASCII этой цифры, который и переносится затем в строку-шаблон. Описанная процедура повторяется затем для регистров CL (минуты) и DH (секунды).


Для вывода строки с временем на экран используется прямое обращение в видеопамяти. В регистр ES заносится сегментный адрес видеобуфера BS00h, а в регистр DI - требуемое смещение видеопамяти к тому месту, начиная с которого мы хотим вывести строку. В регистр SI заносится адрес строки-источника, в регистр СХ - число шагов, а в регистр АН - выбранный нами атрибут символов (красные символы по синему полю). Поскольку перемещение и по строке-шаблону, и по экрану должно осуществляться вперед, командой сld сбрасывается флаг DF. Наконец, циклическое выполнение пары команд

lodsb stosw

приводит к выводу в заданное место экрана всей строки hour.

Выполнив вывод на экран текущего времени, надо снова установить будильник. Для этого сначала запрещается работа ранее установленного будильника, восстанавливается текущее время в регистрах DX и СХ, и вызовом процедуры add_time к текущему времени прибавляется 1 секунда. Далее вызовом функции 06h заново устанавливается будильник, восстанавливаются сохраненные в начале программы обработчика регистры, и, наконец, командой iret обработчик завершает свою работу.

Рассмотрим теперь процедуру прибавления 1 к текущему времени. Она состоит из двух компонентов - подпрограммы add_time, которая организует правильное сложение чисел, обозначающих время, чтобы прибавление 1 секунды к 59 секундам дало 0 секунд и увеличило на 1 число минут (и то же самое для минут) и подпрограммы add_uuit, выполняющей прибавление 1 к упакованному коду BCD.

Подпрограмма add_time переносит число секунд из DH в AL, с помощью подпрограммы add_unit увеличивает его на 1 и возвращает в DH. Подпрограмма add_unit сигнализирует установкой флага CF о необходимости переноса 1 в следующий разряд времени (число секунд составляло 59). Поэтому после возврата из add_iuit проверяется флаг CF и, если он сброшен, т.е. следующий разряд времени модифицировать не надо, подпрограмма add_time завершается. Если же флаг CF установлен, выполняется аналогичная процедура прибавления 1 к числу минут, которое находится в регистре CL. Далее опять анализируется флаг CF, и если он установлен (текущее время было 59 мин 59 с), прибавляется 1 к числу часов. Наконец, подпрограмма завершается командой ret.


Подпрограмма add_unit получает упакованное двоично-десятичное число, к которому надо прибавить 1, в регистре AL. Командой add к нему прибавляется 1, после чего в некоторых случаях образуется правильная сумма, а в некоторых - неправильная. Так, 14h + 1 = 15h, что правильно, однако 19h + 1 = lAh, что неверно. Такого двоично-десятичного числа не существует, а после прибавления 1 к 19 должно получиться 20 (и записано в виде 20h). Коррекцию после сложения BCD-чисел осуществляет команда daa, которая в приведенном примере преобразует lAh в 20h, и которая должна всегда следовать за командой сложения.

Наши двоично-десятичные числа специфичны в том отношении, что они не могут превышать 59. Поэтому после коррекции результат сравнивается с 60h. Если сумма меньше 60h, флаг CF сбрасывается и выполняется команда ret. Если сумма равна 60h, регистр AL обнуляется, флаг CF устанавливается, сигнализируя о переносе 1 в следующий разряд времени (минут или часов) и выполняется та же команда ret. Таким образом, флаг CF процессора в точке возврата из подпрограммы add_unit говорит не о наличии или отсутствии арифметического переноса, а выполняет роль флага "исключительной ситуации" - перехода времени на следующую минуту или на следующий час. Такое нестандартное использование флага CF является общеупотребительным приемом.

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