раз. Прямое обращение к видеобуферу удобно использовать, например, в обработчиках прерываний, где запрещен вызов функций DOS и имеются ограничения на обращение к средствам BIOS.
Пусть по ходу программы необходимо вывести в нижнюю строку экрана предупреждающее сообщение. Для этого в программу надо включить следующие предложения:
;Пример 3-7. Вывод на экран прямой записью в видеопамять;В полях данных, адресуемых через DS. msg db 'Измерения закончены'
msg_len=S-msg ;В программном сегменте mov SI,oflset msg AX,OB800h ES,AX DI,25*80*2 CX,msg_len |
;Длина строки
;О5:51->выводимая строка
mov mov mov mov eld |
;Сегментный адрес видеобуфера
; Будем адресовать через ES
;Смещение к последней строке экрана
;Счетчик цикла вывода символов
;DF=0, движение по строке
;и по экрану вперед
mov AH,3 Ih |
;Атрибут символов — синий по
; голубому
outscr: |
lodsb |
;Взятъ символ из строки в AL
stosw |
;Вывод на экран символа
;из AL и его атрибута из АН
;Цикл
loop outscr
Регистры DS:SI настраиваются на адрес начала выводимой строки; регистры ES:DI — на адрес требуемой позиции в видеобуфере. В регистр СХ надо поместить длину строки в байтах, а флаг DF сбросить, чтобы двигаться по строке вперед. На экран будет выводиться содержимое регистра АХ, в младшем байте которого должен находиться код ASCII выводимого символа, а в старшем байте — атрибут символа, т.е. код цвета символа (в младшем полубайте) и код цвета фона (в старшем полубайте). В примере число 31h образует синие символы по бирюзовому фону. При желании можно выбрать другую комбинацию цветов, выбрав ее с помощью табл. 3.1.
Таблица 3.1. Коды цветов стандартной цветовой палитры
Код | Цвет | Код | Цвет | |
Oh | Черный | 8U | Серый | |
Ш | Синий | 9U | Голубой | |
2h | Зеленый | Ah | Салатовый | |
3h | Бирюзовый | Bh | Светло-бирюзовый | |
4h | Красный | Ch | Розовый | |
5h | Фиолетовый | Dh | Светло-фиолетовый | |
6h | Коричневый | Eh | Желтый | |
7h | Белый | Fh | Ярко-белый |
Глава j
Команды и алгоритмы
Выбирая цвета, следует иметь в виду, что при стандартной настройке видеосистемы, для цвета фона можно использовать лишь значения из левого столбца таблицы; выбор любого яркого цвета из правого столбца приведет в выводу мерцающего символа. Например, атрибут символа Blh образует синий мерцающий символ на бирюзовом фоне (а не синий символ на светло-бирюзовом фоне).
Содержательную часть цикла вывода образуют две команды lodsb и stosw. Первая команда загружает в регистр AL код очередного символа, вторая выводит его вместе с атрибутом, хранящемся в АН, на экран. При этом после каждого выполнения команды lodsb содержимое SI увеличивается процессором на 1, смещая адресацию к следующему символу строки; в то же время каждое выполнение команды stosw увеличивает DI на 2 (потому что команда stosw работает со словами), смещая адресацию на экране на 2 байт, т.е. как раз к позиции следующего символа.
Примеры использования команд cmps и seas можно найти в Приложении.
3.4. Использование подпрограмм
Общая идея использования подпрограмм очевидна: если в программе требуется многократно выполнять один и тот же фрагмент, его можно оформить в виде подпрограммы и вызвать по мере необходимости. Если подпрограмма не требует для своего выполнения никаких параметров и не должна возвращать в основную программу результат своей работы, то дело ограничивается оформлением текста подпрограммы в виде процедуры, завершающейся командой ret, и вызовом этой процедуры с помощью команды call. Как уже отмечалось ранее, подпрограмма может и не образовывать процедуру, а быть просто частью основной программы. Важно только, чтобы у нее была входная метка, и чтобы она завершалась командой call.
В следующем примере подпрограмма delay используется для включения в основной текст программы программных задержек фиксированной величины.
;Примср 3-8. Вызов code segment assume delay proc push CX mov CX,2000 dell: push CX mov CX,0 de!2: loop de!2 pop CX loop dell pop CX ret delay endp main proc |
подпрограммы баз параметров
cs: code, ds: data;Процсдура-подпрограмма;Сохраним СХ основной программы;Счетчик внешнего цикла;Сохраним его;Счетчик внутреннего цикла;Внутрснний цикл (64К шагов);Восстановим внешний счетчик;Внешний цикл (2000 шагов) восстановим СХ программы;Возврат в программу
; Настроим DS ;на сегмент данных ;Функция вывода на экран ;Адрес первой строки ;Будем выводить строки в цикле ;Вызов DOS ;Вызов подпрограммы задержки ;Прибавим к смещению длину строки ;Цикл вызовов DOS ;3авершение программы |
mov AX,data
mov DS,AX
mov AH,09h
mov DX,offset nisgl
mov CX,3 cntrl: hit 2 Hi
call delay
add DX,msg_len
loop cntrl
mov AX,4COOh
hit 21h main endp code ends data segment
msgl db "Процесс стартовал ",13,10,"$' msg_len=S-msgl
msg2 db "Процесс идет ",13,10,"$' msg3 db "Процесс завершается", 13,10,'$' data nds stk segment stack
dw 128 dup О stk ends
end main
В тексте программы сначала описана процедура-подпрограмма, затем основная программа. Как уже отмечалось, порядок их описания роли не играет; важно только, чтобы в завершающей директиве окончания трансляции end был указан в качестве точки входа адрес основной программы (main в нашем примере).
Подпрограмма реализует задержку с помощью вложенных циклов с командой loop, использующей в качестве счетчика шагов регистр СХ. В основной программе этот регистр используется для организации цикла вывода трех строк. Поэтому первое, что должна сделать подпрограмма — это сохранить содержимое регистра СХ, для чего естественно использовать стек. Перед завершающей командой ret регистр СХ должен быть восстановлен. Фрагмент, реализующий задержку, был описан ранее, в разделе 3.2.
Основная программа выводит на экран с помощью функции 09п три строки текста. Для упрощения программы, а также чтобы продемонстрировать некоторые приемы программирования, вывод строк реализован в цикле. Строки сделаны одной длины, и модификация смещения к очередной строке выполняется прибаачением к содержимому регистра DX длины строки. Полезно обратить внимание на организацию цикла в основной программе. В цикл, помимо команды вызова подпрограммы задержки и предложения, модифицирующего регистр DX, включена лишь команда int 2lh. Регистр АН с номером функции заново не настраивается. Это и не нужно, так как DOS, выполняя затребованную операцию, первым делом
ы^Як.
Глава 3
сохраняет все регистры программы, а перед возвратом в программу их восстанавливает. Поэтому, вызывая функции DOS (или BIOS) можно не заботиться о сохранении регистров — их содержимое система на разрушает. Надо только иметь в виду, что многие функции DOS и BIOS после своего завершения возвращают в программу некоторую информацию (число реально введенных символов, доступный объем памяти, номер видеорежима и т.п.). Обычно эта информация возвращается в регистре АХ, однако могут использоваться и другие регистры или их сочетания. Поэтому, обращаясь в программе к системным функциям, необходимо ознакомиться с их описанием и, в частности, посмотреть, какие регистры они могут использовать для возвращаемых значений.
Запустив программу, можно убедиться в том, что строки текста появляются на экране через заметные промежутки времени.
В примере 3-8 подпрограмма не требовала параметров. Чаще, однако, подпрограмма должна принимать один или несколько параметров и возвращать результат. В этом случае необходимо организовать взаимодействие основной программы и подпрограммы. Никаких специальных средств языка для этого не существует; передачу параметров в подпрограмму и из нее программист организует по своему усмотрению. Для передачи параметров как в одну, так и в другую сторону можно использовать регистры общего назначения, ячейки памяти или стек. Например, нетрудно преобразовать подпрограмму delay из примера 3-8 так, чтобы ей можно было передавать величину требуемой задержки. Пусть эта величина (в члсле шагов внешнего цикла) передается в регистре SI.
;Пример 3-8а.Подпрограмма задержки с одним параметром, | |||
передаваемом в регистре SI | |||
delay | proc | ; Процедура - подпрограмма | |
push | CX | ;Сохраним СХ основной программы | |
mov | CX,SI | ;Счетчик внешнего цикла | |
dell: | push | CX | ;Сохраним его |
mov | cx,o | ;Счетчик внутреннего цикла | |
de!2: | loop | de!2 | ;Внутренний цикл (64К шагов) |
pop | CX | ;Восстановим внешний счетчик | |
loop | dell | ;Внешний цикл (2000 шагов) | |
pop | CX | восстановим СХ программы | |
ret | ;Возврат в программу |
Можно пойти еще дальше и составить подпрограмму таким образом, чтобы передаваемый в нее параметр характеризовал время задержки в секундах. Если не связываться с использованием системного таймера в качестве инструмента для определения интервала времени, а по-прежнему реализовывать задержку с помощью процессорного цикла, ее величина будет зависеть от скорости работы конкретного компьютера и должна быть подобрана экспериментально. Приведенный ниже вариант подпрограммы пра
уианды и алгоритмы
;Пример 3-86.Подпрограмма задержки с преобразованием
;параметра, передаваемого в регистре SI
delay proc;Процедура-подпрограмма
push AX;Сохраним все
push BX;используемые
push CX;в подпрограмме
push DX; регистры
mov AX,SI;Первый сомножитель в АХ
mov BX,600;Второй экспериментально
;подобранный сомножитель
mul BX;Произведение в DX:AX
mov СХ,АХ;Нам оно нужно в СХ
dell: push CX;Сохраним его
mov CX,0;Счетчик внутреннего цикла
de!2: loop de!2;Внутренний цикл (64К шагов)
pop CX восстановим внешний счетчик
loop dell;Внешний цикл (2000 шагов)
pop DX;Восстановим
pop CX;все сохраненные
pop BX;в начале подпрограммы
pop AX;регистры
ret;Возврат в программу
Эксперименты показали, что для получения правильной задержки значение параметра, обозначающее число секунд, следует умножать на 600. Поскольку при умножении в системе команд МП 86 первый сомножитель должен находиться в регистре АХ, а второй не может быть непосредственным значением и тоже, следовательно, должен быть помещен в один из регистров, и, к тому же, произведение занимает два регистра DX:AX, приходится сохранять при входе в подпрограмм)' не один регистр, как в предыдущем примере, а 4. Передаваемый в SI параметр переносится в АХ, в ВХ загружается второй сомножитель, а из полученного с помощью команды mul произведения используется младшая часть, находящаяся в АХ. Таким образом, для данного варианта подпрограммы значение задержки не должно превышать 109 с (109 х 600 = 65500, что почти совпадает с максимально возможным значением 65535).
Следует обратить внимание на опасность, подстерегающую нас при выполнении операции умножения. Пусть значение передаваемого параметра состаштяет всего 5. При умножении на 600 получится число 3000, которое безусловно помещается в регистре АХ. Однако операция умножения 16-разрядных операндов
mul BX
всегда, независимо от конкретной величины произведения, помещает его в пару регистров DX:AX, и, следовательно, при небольшой величине Произведения регистр DX будет обнуляться. Поэтому, хотя мы и не ис-
Глава 3
пользуем старшую часть произведения и фактически ее может и не быть, сохранение и последующее восстановление регистра DX является обязательным.
Передача параметров в подпрограмму через регистры общего назначения или даже через сегментные регистры вполне возможна, однако на практике для передачи параметров чаще всего используют стек, хотя бы потому, что регистров немного, а в стек можно поместить любое число параметров. При этом применяется своеобразная методика работы со стеком не с помощью команд push и pop, а с помощью команд mov с косвенной адресацией через регистр ВР, который архитектурно предназначен именно для адресации к стеку. Преобразуем пример 3-86 так, чтобы единственный в этом примере параметр (условная величина задержки) передавался в подпрограмму не через регистр SI, а через стек. Вызов подпрограммы delay в этом случае должен выполняться следующим образом:
push 2000;Проталкиваем в стек значение параметра
call delay;Вызываем подпрограмму delay
Текст подпрограммы подвергнется значительным изменениям:;Пример 3-8в. Передача параметра через стек
delay | ргос | |
push | CX | |
push | BP | |
mov | BP,SP | |
mov | CX,[BP+6] | |
dell: | push | CX |
mov | cx,o | |
de!2: | loop | de!2 |
pop | CX | |
loop | dell | |
pop | BP | |
pop | CX | |
ret |
;Процедура-подпрограмма
;Сохраним СХ основной программы
;Сохраним ВР
;Настроим ВР на текущую вершину стека
;Скопируем из стека параметр
;Сохраним его;,*•
;Счетчик внутреннего цикла }':';-
;Внутренний цикл (64К шагов);#
;Восстановим внешний счетчик i\-:
; Внешний цикл
восстановим ВР
;и СХ программы ||,_
;Возврат и снятие со стека \
;ненужного уже параметра gj>;
Команда call, передавая управление подпрограмме, сохраняет в стеке адрес возврата в основную программу. Подпрограмма сохраняет в стеке еще два 16-разрядных регистра. В результате стек оказывается в состоянии, изображенном на рис. 3.9.
После сохранения в стеке исходного содержимого регистра ВР (в основной программе нашего примера этот регистр не используется, однако в общем случае это может быть и не так), в регистр ВР копируется содержимое указателя стека, после чего в ВР оказывается смещение вершины стека. Далее командой mov в регистр СХ заносится содержимое ячейки стека, на 6 байтов ниже текущей вершины. В этом месте стека как раз находится передаваемый в подпрограмму параметр, как это показано в левом столбце рис. 3.9. Конкретную величину смещения относительно вершины стека надо для каждой подпрограммы определять индивидуально,
Команды и алгоритмы
![]() |
ВР + 4 ВР + 6 |
SP
Рис. 3.9. Состояние стека в подпрограмме после сохранения регистров.
исходя из того, сколько слов сохранено ею в стеке к этому моменту. Напомним, что при использовании косвенной адресации с регистром ВР в качестве базового, по умолчанию адресуется стек, что в данном случае и требуется.
Параметр, полученный таким образом, используется далее в подпрограмме точно так же, как и в примере 3-8а.
Выполнив возложенную на нее задачу, подпрограмма восстанавливает сохраненные ранее регистры и осуществляет возврат в основную программу с помощью команды ret, в качестве аргумента которой указывается число байтов, занимаемых в стеке отправленными туда перед вызовом подпрограммы параметрами. В нашем случае единственный параметр занимает 2 байт. Если здесь использовать обычную команду ret без аргумента, то после возврата в основную программу' параметр останется в стеке, и его надо будет оттуда извлекать (между прочим, не очень понятно, куда именно, ведь все регистры у нас могут быть заняты). Команда же с аргументом, осуществив возврат в вызывающую программу, увеличивает содержимое указателя стека на значение ее аргумента, тем самым осуществляя логическое снятие параметра. Физически этот параметр, как, впрочем, и все остальные данные, помещенные в стек, остается в стеке и будет затерт при дачьнейших обращениях к стеку.
Разумеется, в стек можно было поместить не один, а сколько угодно параметров. Тогда для их чтения надо было использовать несколько команд mov со значениями смещения ВР+6, ВР+8, BP+OAh и т.д.
Рассмотренная методика может быть использована и при дальних вызовах подпрограмм, но в этом случае необходимо учитывать, что дальняя команда call сохраняет в стеке не одно, а два слова, что повлияет на ^величину рассчитываемого смещения относительно вершины стека.
3.5. Двоично-десятичные числа
В гл. 2 уже говорилось о двоично-десятичных числах — специальном формате хранения данных, используемом в ряде технических приложе-;-ний. Часто эти числа называют BCD-числами (от binary-coded decimal, 'двоично-кодированные десятичные числа). Для обработки BCD-чисел (сло-^жения, вычитания, умножения и деления) в МП 86 предусмотрены специальные команды. Рассмотрим этот вопрос на комплексном примере • обработки показаний КМОП-часов реального времени.
140_______________________________________________________________ Главаз
Как известно, в современных компьютеров имеются два независимых таймера. Один из них («часы реального времени») включен в состав микросхемы с очень низким потреблением тока, питается от батарейки или аккумулятора, находящегося на системной плате, и работает даже на выключенной из сети машине. В этом таймере хранится и автоматически наращивается текущее календарное время (год, месяц, день, час, минута и секунда).
После включения компьютера вступает в работу другой таймер, который обычно называют системным. Датчиком сигналов времени для него служит кварцевый генератор, работающий на частоте 1,19318 МГц; сигналы от которого, после пересчета в отношении 65536:1, поступают в контроллер прерываний и инициируют прерывания через вектор 8 с частотой 18,2065 Гц. Эти прерывания активизируют программу BIOS, периодически выполняющую инкремент содержимого четырехбайтовой ячейки памяти с текущим временем, находящейся по адресу 46Ch. После включения машины программы BIOS считывают из часов реального времени текущее время суток, преобразуют его в число тактов системного таймера (т.е. в число интервалов по 1/18,2065 с) и записывают в ячейку текущего времени. Далее содержимое этой ячейки наращивается уже системным таймером, работающим в режиме прерываний.
Для определения текущего времени прикладная программа может вызвать соответствующие функции прерывания 21h DOS (конкретно, с номером 2Ah для получения даты и 2Ch для получения времени суток), а может прочитать время непосредственно из часов реального времени с помощью прерывания lAli BIOS. При этом прерывание lAli позволяет, помимо чтения текущего времени (функция 02h) и текущей даты (функция 04h), выполнять и целый ряд других функций, среди которых мы отметим только возможность установить «будильник», т.е. записать в микросхему часов значение календарного времени, когда часы должны выдать сигнал аппаратного прерывания. Этот сигнал через вектор 70h инициирует обработчик прерываний, входящий в состав BIOS, который проверяет, возникло ли данное прерывание в результате достижения времени установки будильника (часы реального времени могут инициировать прерывания и по других причинам), тестирует заодно батарейное питание микросхемы, а затем посылает в оба контроллера прерываний команды конца прерываний и завершается командой iret. Однако по ходу своего выполнения обработчик прерывания 70U выполняет команду int 4Ah, которая передает управление на обработчик этого прерывания, тоже входящий в состав BIOS. Системный обработчик прерывания 4Ali ничего особенно полезного не делает, в сущности представляя собой просто программу-заглушку. Однако программист имеет возможность записать в вектор 4Ali адрес прикладного обработчика прерываний, который будет активизироваться прерыванием будильника. Функции прикладного обработчика определяет программист.
В примере 3-9 устанавливается прикладной обработчик прерывания 4AU, который сам по себе вызваться никогда не будет, так как по умолчанию будильник часов реального не работает. Если, однако, прочитать си-
Команды и алгоритмы__________________________________________________ 141
схемное время с помощью функции 02U прерывания 1АИ, прибавить к нем>' некоторую величину, например, 1 секунду, и установить будильник на это время {с помощью функции 06h прерывания lAli), то через одну секунду будет активизирован наш обработчик. В примере 3-9 этот процесс сделан бесконечным: в обработчике прерываний будильника снова выполняется чтение времени,'прибавление к нему 1 секунды и установка будильника на новое время. В результате наш обработчик будет вызываться каждую секунду до завершения всей программы.
Помимо служебной функции установки будильника на следующую секунду, обработчик прерываний выполняет и полезную работу: он выводит текущее время в определенное место экрана. Поскольку обработчик активизируется каждую секунду, выводимое значение времени будет обновляться каждую секунду.
Как уже говорилось, в часах реального времени значение времени хранится в упакованных двоично-десятичных чисел. При выполнении арифметических операций с числами BCD (а нашем случае операции заключаются в прибаштении 1) необходимо использовать предназначенные для этого команды процессора. В примере проиллюстрировано использование одной из этих команд, конкретно, команды daa.
Для того, чтобы вывести на экран значение времени, его надо преобразовать в последовательность кодов ASCII. Процедура преобразования упакованных двоично-десятичных чисел в строку символов также вклю-в рассматриваемый пример..
;Пример 3-9. Чтение и обработка показаний часов
;реального времени
.586;Будут использоваться дополнительные команды
assume CS:code,DS:data
code segment use!6
main proc
mov AX.data;Настроим DS наш
mov DS,AX;сегмент данных
;Сохраним исходный вектор 4Ah
mov AX,354Ah
int 2 In
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
;Установим будильник
Глава 3
mov int call mov |
AH,02h lAli add_time AH,06h |
;Чтение текущего времени
;Прибавим I секунду;Установим будильник на это; время int lAli;Остановим программу, чтобы наблюдать прерывания
mov AH.Olh;Функция ввода с клавиатуры
int 2Ш;3авершим программу, прибрав за собой
;Сброс будильника ;О5:ОХ=исходный вектор;Установим исходный вектор ;Завершим программу |
mov AH,07h
int lAli
Ids DX,old_4a
mov AX,254Ah
int 21h
mov AX,4COOh
int 21h
main endp
;Наш обработчик прерывания от будильника
new_4a proc
;Сохраним все регистры;Сохраним еще и;сегментные регистры;Настроим DS на наш;сегмент данных;Прочитаем текущее время;из часов реального времени;Сохраним полученное;тскущее время |
pusha
push DS
push ES
mov AX,seg hour
mov DS,AX
mov AH,02h
hit lAli
push CX
push DX
;Преобразуем время из двоично-десятичных упакованных
;чисел в строку символов ASCII
mov AL,CH;Перенесем часы в AL
call conv;Преобразуем старшую цифру
mov hour,AH;в ASCII и сохраним
and CH,OFh;Вьщелим младшую цифру
add CH,'0' преобразуем в ASCII
mov hour+l.CH.;и сохраним
mov AL,CL;Перенесем минуты в AL
call conv;Преобразуем старшую цифру
mov min,AH;в ASCII и сохраним
and DL,OFh;Выделим младшую цифру
add DL,'0';Преобразуем в ASCII
mov min+l,CL;и сохраним
mov AL,DH;Перенесем секунды в AL
call conv;Преобразуем старшую цифру
mov sec,AH;в ASCII и сохраним
и алгоритмы
and DH,OFh;Выделим младшую цифру
add DH,"0';Преобразуем в ASCII:
mov sec+l,DH;и сохраним
;Вьшедем сформированную строку на экран
mov AX,OB800h;Настроим ES на сегмент
mov ES,AX видеопамяти
mov DI,160-16;Смещение к 72-му знакоместу
mov SI,offset 1юиг;О8:81->строка с временем
mov CX,8;Цикл по 8 символам
mov AH,14h;Атрибут красный по синему
eld;Движение вперед
screen: lodsb;Очередной символ в AL
stosw;Символ+атрибут на экран
loop screen;Цикл
;Сбросим будильник и установим снова на время через 1с
mov AH,07h
hit lAh
; Вернем сохраненное в стеке;текущее время;Прибавим 1 секунду;Установим будильник на;время через 1 секунду восстановим сохраненные;ранее сегментные регистры восстановим все регистры; Выход из обработчика |
pop DX
pop CX
call add_time
mov AH,06h
int 1AU
pop ES
pop DS
popa
iret
new_4a endp
;Подпрограмма прибавления 1 секунды к текущему времени.;В большинстве случаев достаточно увеличить число секунд;на 1, но в конце минуты надо увеличить на 1 число минут,;а в конце часа — увеличить на 1 число часов add_time proc;Прибавим 1 к числу секунд
mov AL,DH ^Перенесем секунды в AL
call add_unit;Прибавим I
mov DH,AL;Перенесем назад в DH
jnc ok;Если CF=0, на выход
;CF=1, прибавим 1 к числу минут
mov AL,CL;Перенесем минуты в AL
call add_umt;Прибавим 1
mov CL.AL;Перенесем назад в CL
jnc ok;Если CF=0, на выход
;Перенос, прибавим 1 к числу часов
mov AL,CH;Перенесем часы в AL
call add_imit;Прибавим 1
mov CH.AL;Перенесем назад в СН
Глава 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