Assembler для начинающих


Время суток



Время суток


    Канал 0 таймера 8253 имеет специальное назначение в IBM PC. Выход
    этого канала таймера подключен к уровню прерывания 0 микросхемы
    8259. Это означает, что всякий раз, когда выход канала 0 имеет
    активный уровень сигнала, возникает прерывание (при условии, что
    все остальное установлено корректно). Процедура самопроверки при
    включении питания инициализирует канал 0 таймера, загружая в него
    число 0. Это дает наибольшее (не наименьшее) значение счетчика,
    которое может записать в него программа. Имея на входе частоту 1.19
    МГц, счетчик считает обратно к нулю чуть быстрее, чем за 55
    миллисекунд. Программа инициализации устанавливает таймер таким
    образом, что он считает непрерывно. Это означает, что прерывание 0
    возникает 18.2 раза в секунду.


 
      Как мы увидим в следующей главе, встроенная система программ
    BIOS использует это постоянное прерывание от таймера, чтобы следить
    за текущим временем. BIOS продвигает часы текущего времени вперед
    всякий раз, когда возникает прерывание. Затем, с помощью
    соответствующих вычислений, Вы можете преобразовать число циклов
    таймера в часы, минуты и секунды.
 
      Почему же выбрано значение 18.2? Почему счетчик не
    программируется так, чтобы давать прерывание 20 раз в секунду, или
    другое "хорошее" число раз? Это объясняет следующий пример.
 
      Системный таймер может выполнять функцию измерения времени
    отличного от времени дня. Время дня прекрасно подходит для
    определения интервалов времени, измеряемых в секундах или минутах.
    Но в некоторых ситуациях, возникающих при управлении
    вводом-выводом, нужно определять интервалы времени порядка одной -
    двух миллисекунд. Обычно программы отсчитывают такие интервалы с
    помощью временного цикла. Программа для такого цикла выглядит
    примерно так:
 
      MOV   CX, LOOP_VALUE
      HERE:LOOP  HERE
 
      Вы выбираете константу LOOP_VALUE так, что цикл выполняется в
    точности нужное число раз. Это очень хороший метод, если вам нужна
    задержка на определенное время. В выше приведенном примере
    начальное значение константы LOOP_VALUE, равное 0FFFFH, дает время
    выполнения около 250-миллисекунд.
 
      Но предположим, что вы хотите понаблюдать за внешним событием,
    и определить, сколко времени займет его наступление. Можно использовать
    вариант временного цикла такого, например, вида:
 
      MOV   CX, 0
      HERE:
      ; --- проверка возникновения события
      IN    AL, DX
      TEST  AL, MASK_BIT
      LOOPNE      HERE
      DONE:
      ; --- CX содержит число итераций цикла
 
      Таким способом вы считаете число итераций цикла, чтобы
    вычислить затраченное на него время. Этот метод предполагает, что
    событие возникнет до того, как содержимое регистра CX второй раз
    достигнет 0. Но если вам нужно измерить что-то с точностью до
    микросекунд, этот метод не удобен, так как каждая итерация цикла
    требует от 10 до 20 микросекунд. Системный таймер дает лучшее
    решение. Поскольку он изменяет свое значение каждые 840 наносекунд,
    вы сможете определить длительность события с точностью до
    микросекунды.
 
      На Фиг. 8.5 показан пример программы, вычисляющей время события
    с помощью системного таймера. В этом примере в качестве
    регистрируемого события используется канал 2 таймера. В первой

            Microsoft (R) Macro Assembler Version 5.00              1/1/80 04:05:19
            Фиг. 8.5 Управление системным таймером                  Page         1-1
 
                                          PAGE    ,132
                                          TITLE   Фиг. 8.5 Управление системным таймером
 
             0000                   STACK   SEGMENT STACK
             0000  0040[                        DW      64 DUP (?)
                   ????
                               ]
             0080                   STACK   ENDS
 
             0000                   CODE    SEGMENT
                                          ASSUME  CS:CODE
             0000                   TIMER   PROC    FAR
             0000  1E                     PUSH    DS        ; Занесение адреса возврата
             0001  B8 0000                      MOV     AX, 0
             0004  50                     PUSH    AX
 
             0005  B0 B6                        MOV     AL, 10110110B   ; Выборка таймера 2
             0007  E6 43                        OUT     43H, AL
             0009  B8 0500                      MOV     AX, 500H
             000C  E6 42                        OUT     42H, AL         ; Таймер 2 установлен на 500 отсчетов
             000E  8A C4                        MOV     AL, AH
             0010  E6 42                        OUT     42H, AL
 
             0012  E8 001D R                    CALL    LOW_TO_HIGH     ; Выборка времени первого перехода с 0 на 1
             0015  8B D8                        MOV     BX, AX          ; Сохранение значения в регистре BX
             0017  E8 001D R                    CALL    LOW_TO_HIGH     ; Выборка времени второго перехода с 0 на 1
             001A  2B D8                        SUB     BX, AX          ; Вычитая получаем длину цикла
             001C  CB                     RET
             001D                   TIMER   ENDP
 
                  Фиг. 8.5 Управление системным таймером (начало)
                                    ;--------------------------------------------
                                    ; Эта подпрограмма ждет перехода с нижнего уровня
                                    ;  сигнала на верхний (с 0 на 1) в таймере 2
                                    ;  и возвращает в регистре AX значение счетчика таймера 0
                                    ;--------------------------------------------
             001D                   LOW_TO_HIGH     PROC    NEAR
             001D  E4 62                        IN      AL, 62H         ; Проверка разряда таймера 2
             001F  A8 20                        TEST    AL, 20H
             0021  75 FA                        JNZ     LOW_TO_HIGH     ; Цикл: сигнал таймера 2 на
                                                            ;  нижнем уровне
             0023                   WAIT_HIGH:
             0023  E4 62                        IN      AL, 62H         ; Проверка разряда таймера 2
             0025  A8 20                        TEST    AL, 20H
             0027  74 FA                        JZ      WAIT_HIGH       ; Цикл: сигнал таймера 2 на
                                                            ;  верхнем уровне
             0029  B0 00                        MOV     AL, 0           ; Послать команду в регистр управления тай-
             002B  E6 43                       OUT     43H, AL         ;  мером 2, которая "замораживает" таймер 2
             002D  90                     NOP
             002E  90                     NOP               ; Задержка, необходимая для 8253
             002F  E4 40                        IN      AL, 40H         ; Чтение младшего байта счетчика
             0031  8A E0                        MOV     AH, AL
             0033  90                     NOP
             0034  E4 40                        IN      AL, 40H         ; Чтение старшего байта счетчика
             0036  86 E0                        XCHG    AH, AL
             0038  C3                     RET               ; Возвращение значения в AX
             0039                   LOW_TO_HIGH     ENDP
             0039                   CODE    ENDS
                                          END
 
            Фиг. 8.5 Системный таймер (продолжение)
 
    части программы этот канал таймера загружается известным значением.
    Здесь мы произвольно выбрали число 500H. Обратите внимание, что эта
    часть программы идентична способу генерации звуков с помощью втого
    канала таймера.
 
      Наша программа вызывает подпрограмму LOW_TO_HIGH, которая
    возвращает значение таймера в тот момент, когда на выходе канала 2
    таймера отмечается переход от низкого к высокому уровню. Программа
    рследит именно за переходом; если бы она регистрировала только
    высокий уровень, было бы неизвестно, стал ли сигнал высоким только
    что или уже готов стать низким. Подпрограмма посылает нуль в
    управляющий регистр таймера (порт 43H), чтобы "заморозить" текущее
    значение канала 0. Это позволяет ей прочитать текущее значение
    таймера, продолжающего счет. Если бы программа временно не
    зафиксировала таймер, она не смогла бы прочитать без ошибки его
    16-битовое значение.
 
      Обратим внимание на то, что подпрограмма на Фиг. 8.5 содержит
    несколько команд NOP. Эти команды записаны в программе для выдержки
    временных соотношений. Если очень внимательно прочитать инструкции
    по микросхеме 8253, мы заметим, что между командами IN и OUT,
    выполняемыми этой микросхемой, проходит не меньше 1 микросекунды.
    Команда NOP занимает как раз достаточно времени, чтобы исключить
    нарушение требований микросхемы по времени.
      После возврата из подпрограммы программа сохраняет в регистре
    BX значение счетчика таймера во время первого перехода с низкого
    уровня на высокий. Затем программа снова вызывает подпрограмму,
    чтобы зарегистрировать следующий переход с низкого уровня на
    высокий на выходе канала 2 таймера. Потом она вычитает одно число
    из другого, чтобы определить время цикла канала 2.
 
      Мы уже говорили о том, что загрузка регистра 0 таймера
    значением счета 0 - очень полезна. Данная программа подтверждает
    это, так как она вычитает два значения таймера, не обращая внимания
    на то, какое из них больше, а какое меньше. Так как канал 0 таймера
    работает асинхронно по отношению к этой программе, нет никакой
    гарантии, что первое читаемое из него число больше второго.
    Например, предположим, что первый переход с низкого на высокий
    уровень происходит, когда таймер 0 имеет значение 100H. После 500H
    циклов значение числа в таймере будет 0FC00H. Счетчик таймера 0
    автоматически "проскочил" от значения 0 к значению 0FFFFH, и
    значение прочитанное вторым оказалось численно больше первого. Но
    из-за того, что регистр таймера снова начинает счет со значения
    0FFFFH, мы всегда можем вычитать эти два числа. При этом иногда
    будет появляться перенос, иногда нет, но разность этих двух чисел
    всегда будет равна числу отсчетов.
 
      Чтобы убедить вас в правильности этого положения, рассмотрим
    случай, когда счетчик загружается числом 8000H. Если первый переход
    возникает при значении 6000H, второй появится при значении 5B00H, и
    разница между ними составляет 500H. Но если первый переход
    возникает при значении 100H, второй возникает при значении 7C00H, и
    разница станет равна 8500H. Чтобы правильно отреагировать на эту
    ситуацию, программа должна была бы проверить, не произошло ли
    переполнение счетчика за время отсчета.
 
      При выполнении этой программы вы обнаружите, что значение в
    регистре BX составляет около 0A00H, и не равно ожидаемому значению
    500H. Так происходит потому, что таймер работает в режиме
    уменьшения содержимого счетчика на два по каждому временному
    импульсу. Чтобы разобраться в работе микросхемы 8253, нужно
    ознакомиться с инструкцией по ее программированию.
 
      Фиг. 8.6 дает сводку для управляющего слова микросхемы 8253.
    Для настройки одного из каналов на конкретный режим работы вы
    выводите это управляющее слово в порт 43H. Мы уже встречались с
    выводом некотрых значений в порт 43H. Чтобы "заморозить" счетчик,
    мы послали в этот порт нуль, а для настройки генератора тональности
    - код 0B6H. Посмотрим, откуда берутся эти значения.
 
      Два старших бита управляющего слова определяют канал таймера.
    Следующие два бита - выполняемую операцию. Когда мы выводим
    значение 0, выбирается таймер 0 и запирание данных в счетчике.
    Следующие 3 бита задают режим работы выбранного таймера. Эти биты
    не играют роли, когда счетчик заперт, но нужны при инициализации
    таймера. Оставшийся бит определяет, будет ли счетчик работать как
    16-битовое двоичное число или как четырехзначное десятичное в
    двоичном представлении.
A


      Формат управляющего слова
        D7   D6   D5    D4   D3   D2     D1   D0
      ЪДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДВДДДДї
      і SC1і SC0і RL1і RL0і M2 і M1 і M0 і BCDі
      АДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДБДДДДЩ
 
      Определение управления
      SC - выбор счетчика:
          SC1       SC0
      ЪДДДДДДДДДВДДДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДї
      і    0        і    0    і  задать счетчик 0       і
      ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
      і    0        і    1    і  задать счетчик 1       і
      ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
      і    1        і    0    і  задать счетчик 2       і
      ГДДДДДДДДДЕДДДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДґ
      і    1        і    1    і  неопределено         і
      АДДДДДДДДДБДДДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДЩ
 
      RL - чтение/загрузка:
        RL1  RL0
      ЪДДДДВДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
      і  0 і      0 і Операция запирания счетчика (см.  і
      і    і        і раздел процедуры READ/WRITE)      і
      ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
      і  1 і      0 і Считать/загрузить старший байт    і
      ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
      і  0 і      1 і Считать/загрузить младший байт    і
      ГДДДДЕДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
      і  1 і      1 і Считать/загрузить младший байт,   і
      і    і        і затем - старший                 і
      АДДДДБДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
 
      M - режим:
        M2  M1  M0
      ЪДДДВДДДВДДДВДДДДДДДДДДДї
      і 0 і 0 і 0 і Режим 0   і
      ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
      і 0 і 0 і 0 і Режим 1   і
      ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
      і X і 1 і 0 і Режим 2   і
      ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
      і X і 1 і 1 і Режим 3   і
      ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
      і 1 і 0 і 0 і Режим 4   і
      ГДДДЕДДДЕДДДЕДДДДДДДДДДДґ
      і 1 і 0 і 1 і Режим 5   і
      АДДДБДДДБДДДБДДДДДДДДДДДЩ
 
      BCD:
      ЪДДДДДДДВДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДї
      і   0 і 16-битовый двоичный счетчик        і
      ГДДДДДДДЕДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДґ
      і   1 і десятичный в двоичном представлении  і
      і     і (BCD) счетчик (4-х разрядный)        і
      АДДДДДДДБДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДЩ
 
          Фиг. 8.6 Программирование таймера/счетчика
           (с разрешения фирмы Intel; приоритет Intel 1981г.)A
      Управляющий код 0B6H, который использовался для генерации
    тональности, можно расписать таким образом:
 
      0B6H = 10110110B = 01 11 011 0
 
      Это управляющее слово выбирает канал 2. Счетчик этого канала
    работает с 16-битовым значением, младший байт которого загружается
    в   счетчик первым. Последный бит показывает, что счет будет
    двоичным. Можно видеть также, что выбирается третий режим работы.
 
      Микросхема 8253 может работать в шести разных режимах. Но для
    наших целей, в конфигурации системы IBM, удобны только два из них.
    Режим 3 устанавливается по умолчанию для всех трех каналов таймера.
    Это режим работы генератора прямоугольных импульсов: половину
    периода выход канала дает нижний уровень, а другую половину периода
    - верхний. Счетчик работает в этом режиме, вычитая из своего
    значения по два, а не по одному. Во время первого обратного счета
    выход низкий, а затем, во время второго счета - высокий. Так как
    счетчик считает по два, выход имеет высокий уровень точно половину
    заданного времени, и низкий тоже точно половину. Из-за того, что
    канал 0 таймера обычно работает в режиме 3, пример на Фиг. 8.5
    заканчивает счет со значением счетчика 0A00H, а не 500H, как
    ожидалось. Каждый отсчет таймера уменьшает содержимое счетчика на
    два.
 
      Так как счетчик считает двойками, он переполняется каждые 27
    микросекунд. Если вы хотите измерять события более длительные, то
    вам придется воспользоваться другим способом измерения времени.
 
      Другой режим таймера, который используется в IBM PC - это режим
    0. Этот режим называют прерыванием по завершению счета. В этом
    режиме таймер не работает непрерывно. После его установки таймер до
    тех пор не начинает считать (единицами), пока в него полностью не
    будет загружено число. Затем счетчик считает в сторону уменьшения с
    частотой синхроимпульсов, пока не достигнет нуля. В этот момент его
    выход становится высоким. Поскольку выход канала 0 таймера
    подключен к прерыванию 0 контроллера 8259, в системе возникает
    прерывание.
 
      Режим прерывания по завершению счета полезен, если вы хотите в
    определенный момент подать программе сигнал с помощью прерывания.
    Так как счетчик ограничен шестнадцатью битами, максимальный
    отсчитываемый интервал времени составляет 55 миллисекунд. Если этот
    интервал слишком мал, нужен другой метод измерения времени.
 
      Если вы хотите измерять интервал времени в секундах, нужно
    оставить таймер в его обычном режиме работы. Система BIOS позволяет
    захватывать управление системой каждые 55 миллисекунд, и в каждый
    такой момент вы можете решить, не исчерпался ли нужный промежуток
    времени.
      Если время нужной вам задержки находится между 55
    миллисекундами и 5 секундами, можно использовать метод без
    использования программ BIOS. Например, вам хочется сделать задержку
    на 150 миллисекунд. Используя режим прерывания по завершению счета,
    вы настраиваете таймер на прерывание через 50 миллисекунд (этому
    соответствует значенние счетчика около 59500). Обработчик
    прерывания программируется так, чтобы, получая управление первые
    два раза, он заново устанавливал таймер на 50 миллисекунд. По
    третьему прерыванию от таймера, когда 150 миллисекунд исчерпаны,
    можно предпринять нужные действия.
 
      При организации задержек через таймер всегда нужна некоторая
    осторожность. Как упоминалось выше, канал 1 таймера выполняет одну
    важную аппаратную функцию. Если вы модифицируете число в канале 1,
    ваша программа может немедленно разрушиться. Использование канала 2
    таймера безопасно. Этот канал подключен только к динамику и выходу
    кассетного магнитофона. Отсюда, очевидно, следует, что нельзя
    использовать канал 2 таймера для отсчета промежутков времени в одно
    время с попытками воспроизводить мелодии через динамик. И наконец,
    BIOS пользуется услугами таймера 0 для различных системных функций.
    При обсуждении BIOS будет видно, что прерывание по времени суток
    управляет не только текущим временем, но также обслуживает и
    двигатель накопителя на дискетах. Перед тем, как изменять настройку
    канала 0 таймера для любых целей, нужно понять, какие существующие
    функции вы можете при этом изменить.




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