Двоично-десятичные числа
Сохранив регистры и настроив 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, которая обнуляет старший полубайт, не затрагивая младшего. Прибавление кода ASCII нуля к коду десятичной цифры образует код ASCII этой цифры, который и переносится затем в строку-шаблон. Описанная процедура повторяется затем для регистров CL (минуты) и DH (секунды).
Рис. 3.9. Алгоритм работы подпрограммы conv.
Для вывода строки с временем на экран используется прямое обращение в видеопамяти. В регистр 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 является общеупотребительным приемом.