Закрыть окно         Список других документов ПЭВМ "Агат"

.шп24

3. Ассемблерные процедуры - примеры управления часами

.шп0

В конце этой главы приведен исходный текст драйвера часов "DClock", для связи с которым требуются три однобайтовых переменных CLPARM1-CLPARM3 (описаны непосредственно за ORGом). Все операции происходят через точку входа "DCLOCK". В регистре X должен находиться номер вызываемой команды (0-6,¤FF). Как работают команды, нетрудно понять из головных комментариев к ним.

Драйвер часов, прежде всего, должен определить разъем, на котором находится модуль. Как уже отмечалось выше, на одном разъеме могут быть установлены несколько устройств с разнесенными адресами доступа, поэтому процедуры поиска и идентификации лучше всего оформлять отдельно для каждого искомого устройства, со своим циклом просмотра разъемов. При таком решении программа поиска устройств принимает вид группы вызовов подпрограмм, каждая из которых определяет номер разъема конкретного устройства, либо его (устройства) отсутствие, что делает программу элегантной, имеющей структурный вид и легко понимаемой.

В приведенном ниже драйвере часов используется специальная команда ¤FF (стандартный номер функции инициализации для драйверов фирмы Ниппель), инициализирующая драйвер и производящая в том числе поиск разъема контроллера. Эта команда должна быть вызвана при выполнении стартовых процедур программы, в противном случае драйвер будет работать как при отсутствии часов.

Алгоритм поиска контроллера основан на попытке записи в ячейки ¤E и ¤F, часов значений 1 и 2 соответсвенно с последующей проверкой и записи значений 3 и 4 туда же, также с последующей проверкой. Кроме того, программа предварительно сохраняет содержимое ячеек ¤E и ¤F и восстанавливает после проверки текущего разъема. По результатам проверки делаются соответствующие выводы: либо номер разъема фиксируется, либо производится переход к следующему. Если контроллер не найден, номер разъема будет установлен равным 0 и подпрограммы управления часами выполняться не будут, чтобы не происходили "неприятные" передергивания портов других плат, а читаться по запросам будут буферированные данные.

Кроме поиска модуля подпрограмма инициализации устанавливает соответствующие стандарту фирмы Ниппель режимы работы часов и сбрасывает флаги разрешения прерываний.

Чтобы узнать текущее время или дату, нужно выполнить команду фиксации значений (0) и запросами 1-3 прочитать время, дату или день недели.

Возникает резонный вопрос, зачем запрос времени или даты делается двухшаговым, через фиксацию значений? На то есть две причины, во-первых, такой способ позволяет доступать ко всей информации быстрее, т.к. не требуются повторные обращения к контроллеру, и исключается соответственно лишнее время ожидания готовности. Во-вторых, и это главная причина, такой способ исключает возникновение серьезных ошибок датирования. В самом деле, например, Вам нужно маркировать файлы текущим временем и датой, и Вы запрашиваете сначала время, допустим, это будет 23 часа 59 минут 59 секунд, помещаете его в заголовок файла, а затем запрашиваете дату, и как раз в этот момент началось обновление информации, и к моменту прихода готовности уже наступят следующие сутки, в результате чего Ваш файл "помолодеет" сразу на сутки, или вернее, перенесется во времени на сутки вперед. В предложенном варианте такого никогда не случится, т.к., зафиксировав сначала параметры, Вы можете размещать их в любой последовательности в любое время, хоть через несколько секунд.

Так как сигнал готовности исчезает за 244 мкс до начала обновления информации, у нас есть только 244 мкс для того, чтобы успеть считать всю нужную нам информацию. Подпрограмма FIXPAR производит это за 240 мкс, а значит, выполняет все предъявляемые временные критерии. В остальном, методы доступа к часам можно легко выяснить из текста драйвера, ниже мы обсудим только детали, не отраженные в драйвере, по причине неиспользования им режима прерываний.

Прежде всего, мы отметим один из важнейших моментов в программировании операций над информацией в часах. Если Вы используете прерывания от часов (или от других устройств, в программах обработки которых используется обращение к часам), Вы должны в обязательном порядке в начале операции по доступу к любым ячейкам часов запрещать прерывания. Эта необходимость вызвана растянутостью операций доступа к ячейкам часов на несколько команд, в течениe которых вполне может произойти прерывание. Например, если Вы занесли в ¤C0X6 адрес и в этот момент придет прерывание от "Nippel Clock Card", программа обработки прерывания волей-неволей произведет чтение хотя бы регистра C, испортив тем самым установленный ранее Вашей программой адрес. Поэтому, например, для чтения/записи байтов из ячеек могут быть рекомендованы приведенные ниже две процедуры RDBYTE и WRBYTE.

.дв
.ао0
.лв
*-------------------------*
* Пpoчитaть бaйт из чacoв *
*-------------------------*
ADDRESS  DS  1           Текущий адрес ячейки КР512ВИ1

RDBYTE   PHP             Coxpaнить cocтoяниe флaгa 'I'
         SEI             Зaпpeтить пpepывaния
         LDX SLOT        - Установить адрес
         LDA ADDRESS
         STA ¤C086,X     - ;
         LDA ¤C087,X     Прочитать данные
         PLP             Boccтaнoвить cocтoяниe флaгa 'I'
         RTS
.нф

.дв
.ао0
*------------------------*
*  Записать байт в часы  *
*------------------------*
WRBYTE   PHP             Сохранить состояние флага 'I'
         SEI             Запретить прерывания
         PHA
         LDX SLOT        - Установить адрес
         LDA ADDRESS
         STA ¤C086,X     - ;
         PLA             - Записать байт
         STA ¤C087,X     - ;
         PLP
         RTS
.нф
.ао1
.ов

Обе эти процедуры используют ячейку ADDRESS, в которую предварительно нужно занести текущий адрес. Если в программе обработки прерываний использовать те же процедуры RDBYTE и WRBYTE для доступа к ячейкам, то необходимо позаботиться о сохранении содержимого ячeйки ADDRESS на время обработки прерывния.

Обратите внимание, что в начале обeих процедур запоминается текущее состояние регистра признаков и устанавливается маска прерываний. Это гарантирует, что между установкой адреса и чтением/записью данных не произойдет прерывание и не будет испорчен установленный Вами адрес. Тем не менее, эти процедуры не гарантируют правильность работы во всех случаях. Например, если Вы производите модификацию отдельных битов и пришедшее между чтением старого и записью нового значения прерывание произведет изменение своей части информации в этом же регистре, то записываемая Вами информация будет неверной, так как не будет содержать измененной программой обработки прерывания части. В этом случае нужно заключать в скобки "PHP SEI ... PLP" весь программный код, производящий модификацию:

.дв
.ао0
.лв
*-------------------------------------------------*
* Устанавливаем разрешение циклических прерываний *
*-------------------------------------------------*
SETPIE   PHP             Сохранить состояние флага 'I'
         SEI             Запретить прерывания
         LDA #¤B         - Установить адрес ячейки
         STA ADDRESS     - ;
         JSR RDBYTE      Прочитать ячейку ¤0B
         ORA #:01000000  Установить бит PIE в единицу
         JSR WRBYTE      Записать в ячейку ¤0B
         PLP
         RTS
.нф
.ао1
.ов

Как мы уже отмечали выше, режим автоматического перехода на летнее время не используется в модуле "Nippel Clock Card" из-за несовпадения его правил с принятыми в свое время в СССР. Кроме того, в США первым (1) днем недели считается воскресенье, а последним (7) суббота. У нас же под единицей подразумевается понедельник, под семеркой воскресенье.

Нужно заметить, что отсутствие автоматического перехода на летнее время, в принципе, можно компенсировать, реализовав его программно. Для этого зарезервирована ячейка памяти ¤0E, способная принимать два значения, определяющих тип текущего времени. Если в ней содержится ¤55 - сейчас зимнее время, если ¤AA - летнее. Если же в ней какое-либо другое число, то это значит, что тип текущего времени не определен (часы не были установлены и т.д.) и соответственно требует его определения.

Драйвер часов после фиксации времени и запроса типа текущего времени (ячейка ¤E) должен вычислить момент перехода с текущего времени на альтернативное (т.е. с летнего на зимнее или наоборот), по принятым у нас правилам и проверить, не перейден ли этот рубеж текущим временем, если такое случилось, нужно изменить тип времени и скорректировать само время в соответствующую сторону.

Так или иначе, но автор этих строк, а равно и приведенных здесь программ, пока не реализовал этот механизм в своем драйвере, т.к. он показался мне сложным в реализации (в основном имеется ввиду алгоритм нахождения момента перехода с одного времени на другое), и излишне громоздким для того, чтобы выполнять его при каждом запросе. Тем не менее, есть еще способ выполнять этот алгоритм только при инициализации драйвера, но работающих по ночам людей это не спасет от ошибок во времяисчислении. Так или иначе, вопрос о реализации такого механизма будет рассмотрен автором и возможно включен в состав драйвера часов программы "Disk Manager".

Помимо ячейки ¤0E, зарезервированными для работы "Nippel OS" считаются все ячейки до ¤2F включительно, т.е. свободными для использования можно считать 16 ячеек от ¤30 до ¤3F. Teм нe мeнee, нужнo быть ocтopoжным в иx иcпoльзoвaнии, тaк кaк нepeшaeмoй впpямую пpoблeмoй ocтaeтcя coглacoвaниe иcпoльзoвaния этиx ячeeк из paзныx пpoгpaмм.

Мы рассказали вам об основных приемах написания программ на языке ассемблера для управления часами и надеемся, что приведенной информации будет достаточно для написания надежных и качественных программ.

В конце главы хотелось бы отметить еще одну немаловажную особеннность функционирования часов, вернее не часов, а ЭВМ Агат 9. Из-за ошибки разработчика, нельзя включать одновременно внутренние прерывания ЭВМ Агат 9 (обращаться по адресу ¤C040) и разрешать прерывания от "Nippel Clock Card", что приведет к логическим конфликтам (контроллер часов спроектирован с учетом этого дефекта и защищает себя и Агат 9 от электрических конфликтов на шине ЭВМ) и сбоям в работе программы. Можно сказать, что обращение по адресу ¤C020 (выключение внутренних прерываний) должно быть первой командой перед включением прерываний из часов. Отсюда следует, что нет возможности использовать прерывание NMI (50Гц синхронно с кадровой разверткой) и прерывания IRQ, исходящие от "Nippel Clock Card".

.ао0
.лв
.сс
 SBTL 'Драйвер часов'
**********************************************************
*                                                        *
*      Драйвер ввода/корректировки текущего времени      *
*                                                        *
*      Пример управления часами "Nippel Clock Card"      *
*                                                        *
*            из программ на языке ассемблера.            *
* ______________________________________________________ *
*                                                        *
*   (C) 1993, Ниппель, by ALV Software, Автор: А.Голов   *
*                                                        *
**********************************************************
 ORG ¤2000
*========================================================*
CLPARM1 EQU ¤2F0 Первый параметр
CLPARM2 EQU ¤2F1 Второй параметр
CLPARM3 EQU ¤2F2 Третий параметр

*========================================================*
*          Вызов драйвера управления часами              *
* ------------------------------------------------------ *
*  Вход: X - команда драйверу управления часами          *
*    0 - фиксация текущей информации                     *
*    1 - запрос информации о времени                     *
*    2 - запрос информации о дате                        *
*    3 - запрос информации о дне недели                  *
*    4 - установка нового времени                        *
*    5 - установка новой даты                            *
*    6 - установка нового дня недели                     *
*  ¤FF - инициализация драйвера часов                    *
*  ----------------------------------------------------  *
*  Выход: В соответствии с командой                      *
*========================================================*
DCLOCK  CPX #¤FF        Инициализация
        BEQ DCLKINT     = Выполнение инициализации
        CPX #7
        BCS DCLOCKE     = Нет такой команды
        TXA             - Вызов подпрограммы
        ASL A
        TAX
        LDA DCLOCKA,X
        PHA
        LDA DCLOCKA+1,X
        PHA
DCLOCKE RTS             - ;

*--------------------------------------*
* Список подпрограмм исполнения команд *
*--------------------------------------*
DCLOCKA DDB FIXPAR-1  Фиксация информации
        DDB INQTIM-1  Запрос времени
        DDB INQDAT-1  Запрос даты
        DDB INQDAY-1  Запрос дня недели
        DDB SETTIM-1  Установка времени
        DDB SETDAT-1  Установка даты
        DDB SETDAY-1  Установка дня недели
.сс
*--------------------------------*
*  Инициализация драйвера часов  *
*--------------------------------*
DCLKINT  LDA #¤10
DCLKINT0 STA CSLOT
         JSR DCLKINV    Проверить на этом разъеме
         BCS DCLKINT1   = Здесь нет часов
         LDY #¤A        - Инициализировать регистр A
         LDA #:00101111 Тип кварца и прерывания 2 Гц.
         JSR CWRBYTE    - ;
         LDY #¤B        - Инициализировать регистр B
         LDA #:00000110 Данные в двоичном, ход 24 часа
         JSR CWRBYTE    прерывания запрещены - ;
         JMP FIXPAR     = Зафиксировать время
DCLKINT1 LDA CSLOT      - Перейти к следующему разъему
         CLC
         ADC #¤10
         CMP #¤70
         BCC DCLKINT0   - ;
         LDA #0         - Признак отсутствия часов
         STA CSLOT      - ;
         RTS

*-----------------------------*
*  Проверка текущего разъема  *
*-----------------------------*
DCLKINV  LDY #¤E      - Сохранить ячейки ¤E и ¤F
         JSR CRDBYTE  Прочитать байт
         STA CWORK0
         INY
         JSR CRDBYTE  Прочитать байт
         STA CWORK1   - ;
         LDA #2       - Записать #2 в ячейку ¤F
         JSR CWRBYTE  Записать байт - ;
         DEY          - Записать #1 в ячейку ¤E
         LDA #1
         JSR CWRBYTE  Записать байт - ;
         JSR CRDBYTE  - Проверить записалось ли
         CMP #1
         BNE DCLKINVR = Не записалось
         INY
         JSR CRDBYTE  Прочитать байт
         CMP #2
         BNE DCLKINVR = Не записалось - ;
         LDA #4       - Записать #4 в ячейку ¤F
         JSR CWRBYTE  Записать байт
         DEY          - Записать #3 в ячейку ¤E
         LDA #3
         JSR CWRBYTE  Записать байт - ;
         JSR CRDBYTE  - Проверить записалось ли
         CMP #3
         BNE DCLKINVR = Не записалось
         INY
         JSR CRDBYTE  Прочитать байт
         CMP #4
         BNE DCLKINVR = Не записалось
         CLC          -- Часы найдены
         DFB @44      Пропуск SECа
DCLKINVR SEC          -- Часов нет
DCLKINVE LDY #¤E      - Востановить ячейки ¤E и ¤F
         LDA CWORK0
         JSR CWRBYTE  Записать байт
         INY
         LDA CWORK1
         JMP CWRBYTE  Записать байт - ; -- ;

*----------------------------------*
*  Фиксация текущего времени/даты  *
* -------------------------------- *
*  Вход: -//-                      *
*  ------------------------------- *
*  Выход: информация зафиксирована *
*----------------------------------*
FIXPAR  LDA CSLOT
        BEQ FIXPARE    = Часов нет
FIXPAR0 LDY #¤A        - Прочитать бит готовности
        JSR CRDBYTE
        AND #:10000000 'UIP'
        BNE FIXPAR0    = Информация не готова
        LDY #4         - Часы
        JSR CRDBYTE    Прочитать байт
        STA CFIXDCLK   - ;
        LDY #2         - Минуты
        JSR CRDBYTE    Прочитать байт
        STA CFIXDMIN   - ;
        LDY #0         - Секунды
        JSR CRDBYTE    Прочитать байт
        STA CFIXDSEC   - ;
        LDY #9         - Год
        JSR CRDBYTE    Прочитать байт
        STA CFIXDYER   - ;
        LDY #8         - Месяц
        JSR CRDBYTE    Прочитать байт
        STA CFIXDMON   - ;
        LDY #7         - Число
        JSR CRDBYTE    Прочитать байт
        STA CFIXDNUM   - ;
        LDY #6         - День недели
        JSR CRDBYTE    Прочитать байт
        STA CFIXDDAY   - ;
FIXPARE RTS

*-------------------------------------*
*       Запрос текущего времени       *
* ----------------------------------- *
*  Вход: зафиксированное ранее время  *
*  ---------------------------------  *
*  Выход: CLPARM1 - час               *
*         CLPARM2 - минута            *
*         CLPARM3 - секунда           *
*-------------------------------------*
INQTIM LDA CFIXDCLK - Час
       STA CLPARM1  - ;
       LDA CFIXDMIN - Минута
       STA CLPARM2  - ;
       LDA CFIXDSEC - Секунда
       STA CLPARM3  - ;
       RTS
.сс
*------------------------------------*
*        Запрос текущей даты         *
* ---------------------------------- *
*  Вход: зафиксированная ранее дата  *
*  --------------------------------  *
*  Выход: CLPARM1 - год              *
*         CLPARM2 - месяц            *
*         CLPARM3 - число            *
*------------------------------------*
INQDAT LDA CFIXDYER - Год
       STA CLPARM1  - ;
       LDA CFIXDMON - Месяц
       STA CLPARM2  - ;
       LDA CFIXDNUM - Число
       STA CLPARM3  - ;
       RTS

*-------------------------------------------*
*        Запрос текущего дня недели         *
* ----------------------------------------- *
*  Вход: зафиксированный ранее день недели  *
*  ---------------------------------------  *
*  Выход: CLPARM1 - день недели             *
*-------------------------------------------*
INQDAY LDA CFIXDDAY
       STA CLPARM1
       RTS

*---------------------------------*
*    Установка нового времени     *
* ------------------------------- *
*  Вход: CLPARM1 - час            *
*        CLPARM2 - минута         *
*        CLPARM3 - секунда        *
*  -----------------------------  *
*  Выход: установлено             *
*---------------------------------*
SETTIM  LDA CSLOT
        BEQ SETTIME     = Часов нет
        JSR CSTOP       Остановить часы
        LDA CLPARM1     - Час
        STA CFIXDCLK
        LDY #4
        JSR CWRBYTE     Записать байт - ;
        LDA CLPARM2     - Минута
        STA CFIXDMIN
        LDY #2
        JSR CWRBYTE     Записать байт - ;
        LDA CLPARM3     - Секунда
        STA CFIXDSEC
        LDY #0
        JSR CWRBYTE     Записать байт - ;
        LDY #¤A         - Сброс делителя отсчета секунды
        JSR CRDBYTE     Прочитать байт
        AND #:10001111
        ORA #:01110000
        LDY #¤A         Регистр А
        JSR CWRBYTE     Записать байт
        LDY #¤A         Регистр А
        JSR CRDBYTE     Прочитать байт
        AND #:10001111
        ORA #:00100000
        LDY #¤A         Регистр А
        JSR CWRBYTE     Записать байт
        JSR CPUSK       Запустить часы
SETTIME RTS

*---------------------------------*
*      Установка новой даты       *
* ------------------------------- *
*  Вход: CLPARM1 - год            *
*        CLPARM2 - месяц          *
*        CLPARM3 - число          *
*  -----------------------------  *
*  Выход: установлено             *
*---------------------------------*
SETDAT  LDA CSLOT
        BEQ SETDATE   = Часов нет
        JSR CSTOP     Остановить часы
        LDA CLPARM1   - Год
        STA CFIXDYER
        LDY #9
        JSR CWRBYTE   Записать байт - ;
        LDA CLPARM2   - Месяц
        STA CFIXDMON
        LDY #8
        JSR CWRBYTE   Записать байт - ;
        LDA CLPARM3   - Число
        STA CFIXDNUM
        LDY #7
        JSR CWRBYTE   Записать байт - ;
        JSR CPUSK     Запустить часы
SETDATE RTS

*----------------------------------*
*    Установка нового дня недели   *
* -------------------------------- *
*  Вход: CLPARM1 - день недели     *
*  ------------------------------  *
*  Выход: установлено              *
*----------------------------------*
SETDAY  LDA CSLOT
        BEQ SETDAYE   = Часов нет
        JSR CSTOP     Остановить часы
        LDA CLPARM1   - День недели
        STA CFIXDDAY
        LDY #6
        JSR CWRBYTE   Записать байт - ;
        JSR CPUSK     Запустить часы
SETDAYE RTS

*------------------------*
*  Остановить ход часов  *
*------------------------*
CSTOP LDY #¤A
      JSR CRDBYTE
      AND #:10000000
      BNE CSTOP       = Ожидание готовности
      LDY #¤B
      JSR CRDBYTE     Прочитать байт
      ORA #:10000000
      JMP CWRBYTE     Записать байт
.сс
*-----------------------*
*  Запустить ход часов  *
*-----------------------*
CPUSK LDY #¤B
      JSR CRDBYTE     Прочитать байт
      AND #:01111111
      JMP CWRBYTE     Записать байт

*----------------------------*
*  Чтение байта по адресу Y  *
*----------------------------*
CRDBYTE LDX CSLOT   Текущий разъем
        TYA
        STA ¤C086,X Порт адреса
        LDA ¤C087,X Порт данных
        RTS

*-----------------------------*
*  Запись данных по адресу Y  *
*-----------------------------*
CWRBYTE PHA
        LDX CSLOT   Текущий разъем
        TYA
        STA ¤C086,X Порт адреса
        PLA
        STA ¤C087,X Порт данных
        RTS

*-----------------------------------*
* Текущая информация о времени/дате *
*-----------------------------------*
CFIXDCLK DFB 23 Час 
CFIXDMIN DFB 30 Минута
CFIXDSEC DFB 00 Секунда
CFIXDYER DFB 69 Год
CFIXDMON DFB 07 Месяц
CFIXDNUM DFB 30 Число
CFIXDDAY DFB 3  День недели
CSLOT    DFB 0  Разъем контроллера *¤10
CWORK0   DFB 0  Рабочие:
CWORK1   DFB 0

*========================================================*
.ао1
.ов
.ст Описание часов 3

Закрыть окно         Список других документов ПЭВМ "Агат"