Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Финогенов-основы_языка_ассемблера.doc
Скачиваний:
26
Добавлен:
17.09.2019
Размер:
3.35 Mб
Скачать

Глава 3

ok: ret ' ;Выход из подпрограммы

add_time endp

Подпрограмма прибавления 1 к двухразрядному BCD-числу,

Обозначающему 1 разряд времени (секунды, минуты или

;часы)

;При вызове: AL — время в BCD (секунды, минуты или часы)

;При возврате: AL — результат сложения в BCD

add_unit proc

add AL,1 ; Прибавим 1

daa ;Коррекция после сложения

cmp AL,60h ;Надо ли корректировать

;следующий разряд времени'

jb done ;Нет, сумма < 60

mov AL,Oh ;Да, сумма=60, должно быть О

stc ;Установим CF, как признак

;переноса

jmp donel ;И на выход

done: clc ;Сбросим CF (переноса нет)

donel: ret ;Выход из прерывания

addjanit endp

; Подпрограмма преобразования старшей половины ;упакованного BCD-числа в регистре AL в код ASCII символа ;в регистре АН conv ргос

mov AH,0 ;Обнулим АН

- shl AX,4 ;Сдвиг старшего полубайта AL

;в АН

add АН,'0' ;Преобразуем в код ASCII

ret

conv endp code ends ;Сегмент данных data segment usc!6

old_4a hour db min db sec data stk

dd 0 0,0,':' 0,0,':' 0,0

db

ends

segment stack

dw 128 dup(')

ends

end main

stk


;Ячейка для исходного вектора ;Поле для формирования ;строки ASCII с текущим ;временем

В примере 3-9 используются несколько команд, отсутствующих в МП 86: команды сохранения в стеке и восстановления всех регистров общего назначения pusha и рора, а также команда сдвига shl с числовым операн­дом. Для того, чтобы эти команды распознавались ассемблером, в про-

Команды и алгоритмы 145

грамму включена директива .586 (можно было бы обойтись и директивой .386). В этом случае необходимо оба сегмента объявить с описателем use!6.

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

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

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

146

Глава '3

ботчика это исходное значение не восстановить, будет неминуемо разрух шсна DOS.

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

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

Подпрограмма conv преобразования BCD-цифры в код ASCII состоит всего из трех предложений, не считая заключительной команды rcl. Двух-разрядиос BCD-число перелается в подпрограмму в регистре AL. После об­нуления регистра АН, который будет служить приемником для образова­ния конечного результата, содержимое AL сдвигается командой shl влево на 4 бит, в результате чего старший полубайт регистра AL, т.е. старшая цифра числа, перемещается в регистр АН (рис. 3.10). Двоично-десятичная цифра представляет собой просто двоичное представление цифры; прибав­ление к се коду кода символа "О" (числа ЗОИ) дает код ASCII этой цифры. Мы преобразовали пока только старший полубайт регистра СН. Для выделения младшего полубайта на регистр СН накладывается маска OFh,

АН

AL

| 0101 | 0111 PerHcip .AX до сдвига 5 7 =57 (BCD)

АН

AL

0101

0111 |

Регистр .АХ после сдвига

ООП 0000

Прибавление '0'= 30h

АН

ООП I 0101 I Регистр АН после прибавления 'О' 3 5 35h = код ASCII цифры 5

Рис. ЗЛО. Алгоритм работы подпрограммы conv.

Командной алгоритмы 147

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

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

lodsb stosw

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

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

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

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

Подпрограмма add_uuU получаст упакованное двоично-десятичное число, к которому надо прибавить 1, в регистре AL. Командой add к нему Прибавляется 1, после чего в некоторых случаях образуется правильная сумма, а в некоторых — неправильная. Так, I4h + 1 = I5h, что правильно, однако 19h + 1 = IAh, что неверно. Такого двоично-десятичного числа не существует, а после прибавления 1 к 19 должно получиться 20 (и записа-

148 Глава 3

но в виде 20h). Коррекцию после сложения BCD-чисел осуществляет ко­манда daa, которая в приведенном примере преобразует lAh в 20h, и которая должна всегда следовать за командой сложения.

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