Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Доступ к порту #1F в TR-DOS 5.03

(«ZX-Ревю» 1—2/1997)
Дата последнего редактирования: 15.02.2003.

Программируя дисковые операции на уровне портов ввода-вывода, вы неизбежно столкнётесь с необходимостью получить значение регистра состояния из порта #1F. Как же это сделать? Известно, что адреса регистров микроконтроллера появляются в адресном пространстве только в момент работы дисковой системы. Из бейсик-системы регистры недоступны. Не сделано также никаких попыток обеспечить прямой доступ к ним из дисковой системы. Несмотря на это, «достать» микроконтроллер всё-таки возможно, для этого используются «обрывки» программ ОС. По-другому, к сожалению, не получится. Опубликованная в «ZX-Ревю» 4—5/1996 информация о том, что в ПЗУ TR-DOS 5.03 по адресу #09BF стоит последовательность команд IN A,(#1F) и RET, не подтвердилась. В фирменной версии ПЗУ по этому адресу вы можете найти лишь коды #FF. Ещё один экзотический способ доступа к портам (передаём управление в ПЗУ TR-DOS за несколько тактов до прерывания 2-го рода, а процедура обработки прерывания получает считанное из порта значение) теоретически неплох, но практически вряд ли осуществим (а если промежуток между двумя прерываниями непостоянен, этот метод вообще неприменим).


В книге А.Ларченко и Н.Родионова «ZX Spectrum и TR-DOS для пользователей и программистов» приводится следующий алгоритм считывания регистра состояния микроконтроллера:

Я пользовался этим способом, и всё вроде бы было нормально. Но я стал замечать, что в некоторых случаях при обращении к процедуре считывания регистра состояния компьютер зависал или сбрасывался. Пытаясь найти причину, я выяснил следующее:


Алгоритм работает неправильно, если 0-й бит регистра состояния равен 1 (контроллер занят выполнением какой-либо команды).


Действительно, рассмотрим все возможные случаи работы подпрограммы TR-DOS по адресу #3F33 в зависимости от считанного из порта #1F числа, а заодно разберёмся, почему перед её вызовом необходимо устанавливать описанные выше параметры.


1) Биты 0—6 равны нулю (?0000000).

#3F33:  IN      A,(#1F)
        LD      B,A
        AND     #7F      ;Выделяем биты 0-6.
        RET     Z        ;Выход.

2) 6-й бит равен 1, 0-й бит равен 0 (?1?????0).

#3F33:  IN      A,(#1F)
        LD      B,A
        AND     #7F
        RET     Z        ;Не выходим.
        LD      HL,#29D8
        AND     #40      ;Проверяем 4-й бит.
        JR      NZ,#3F4B ;Условие выполняется.

#3F4B:  LD      A,#D0
        OUT     (#1F),A
        LD      A,B
        AND     #1       ;Проверяем 0-й бит.
        JP      NZ,#3EE7 ;Условие не выполняется.
        IN      A,(#3F)  ;Регистр дорожки равен 0.
        OR      A
        JR      NZ,#3F5F ;Условие не выполняется.
        IN      A,(#5F)  ;Регистр сектора равен #0A.
        CP      #0A
        RET     Z        ;Условие выполняется.

3) 6-й бит равен 0, 2-й бит равен 0, 0-й бит равен 0, какой-то из битов 1, 3, 4, 5 равен 1 (?0???0?0).

#3F33:  IN      A,(#1F)
        LD      B,A
        AND     #7F
        RET     Z        ;Условие не выполняется.
        LD      HL,#29D8
        AND     #40      ;Проверяем 4-й бит
        JR      NZ,#3F4B ;Условие не выполняется.
        LD      A,B
        AND     #4
        JR      Z,#3FA0  ;Условие выполняется.

#3FA0:  DEC     D        ;Получили 0.
        JP      Z,#3F48  ;Условие выполняется.

#3F48:  LD      HL,#29E2
        LD      A,#D0
        OUT     (#1F),A
        LD      A,B
        AND     #1       ;Проверяем 0-й бит.
        JP      NZ,#3EE7 ;Условие не выполняется.
        IN      A,(#3F)  ;Регистр дорожки равен 0.
        OR      A
        JR      NZ,#3F5F ;Условие не выполняется.
        IN      A,(#5F)  ;Регистр сектора равен #0A.
        CP      #0A
        RET     Z        ;Условие выполняется.

4) 0-й бит равен 1 (???????1).


В этом случае, независимо от значения других битов, управление будет передано по адресу #3EE7. Там расположена процедура, портящая значение некоторых переменных TR-DOS и завершающая свою работу так:

        ........
#01F3:  LD      SP,(#5D1C)
        LD      HL,(#5D1A)
        LD      BC,(#5D0F)
        LD      B,0
        JP      (HL)

Так как в ячейках #5D1A—#5D1D, вообще говоря, могут содержаться произвольные значения, дальнейшее выполнение программы непредсказуемо. Вдобавок и значение, считанное из порта #1F и хранящееся в регистре B (ради которого всё и затевалось), будет потеряно.


Теперь я приведу написанную мной процедуру, которая правильно (всегда!) определяет значение регистра состояния. При своей работе она не изменяет значение ни одной системной переменной TR-DOS.

;***************************************
;Процедура STATUS возвращает содержимое
;регистра состояния.
;
;Вход:  A - содержимое регистра дорожки,
;       B - содержимое регистра сектора,
;       которые будут установлены после
;       выхода из процедуры.
;
;Выход: A - значение, считанное из порта #1F.
;Прерывания после выхода запрещены!

STATUS  DI
        LD      (RG_D+1),A  ;Дорожка.
        LD      A,B
        LD      (RG_S+1),A  ;Сектор.

;Сохраняем содержимое ячеек, которые
;могут быть испорчены:

        LD      A,(#5D0E)
        LD      (ST1+1),A
        LD      A,(#5D0C)
        LD      (ST2+1),A
        LD      A,(#5CB6)
        LD      (ST3+1),A
        LD      A,(#5D1F)
        LD      (ST4+1),A
        LD      A,(#5C3A)
        LD      (ST5+1),A
        LD      A,(#5D17)
        LD      (ST6+1),A
        LD      HL,(#5D1A)
        LD      (ST7+1),HL
        LD      HL,(#5D1C)
        LD      (ST8+1),HL
        LD      HL,(#5CF8)
        LD      (ST9+1),HL

;Устанавливаем содержимое некоторых
;ячеек для правильной работы:

        LD      A,#FF
        LD      (#5D0C),A
        LD      (#5D1F),A
        DEC     A
        LD      (#5D0E),A
        LD      A,#F4
        LD      (#5CB6),A

        LD      HL,S_SPEC
        LD      (#5D1A),HL
        LD      HL,0
        ADD     HL,SP
        LD      DE,-12
        ADD     HL,DE
        LD      (#5D1C),HL

        LD      A,0     ;0 в регистр
        LD      C,#3F   ;дорожки.
        CALL    TO_WG93
        LD      A,#0A   ;#A в регистр
        LD      C,#5F   ;сектора.
        CALL    TO_WG93
        LD      D,1
        LD      IX,16179
        CALL    TO_DOS  ;Определили #1F.

;Теперь восстанавливаем содержимое
;регистров дорожки и сектора:

RG_D    LD      A,0
        LD      C,#3F
        CALL    TO_WG93
RG_S    LD      A,0
        LD      C,#5F
        CALL    TO_WG93

;Восстанавливаем ранее запомненное
;содержимое ячеек:

ST1     LD      A,0
        LD      (#5D0E),A
ST2     LD      A,0
        LD      (#5D0C),A
ST3     LD      A,0
        LD      (#5CB6),A
ST4     LD      A,0
        LD      (#5D1F),A
ST5     LD      A,0
        LD      (#5C3A),A
ST6     LD      A,0
        LD      (#5D17),A
ST7     LD      HL,0
        LD      (#5D1A),HL
ST8     LD      HL,0
        LD      (#5D1C),HL
ST9     LD      HL,0
        LD      (#5CF8),HL

        LD      A,B
        RET

;Сюда будет передано управление, если
;0-й бит регистра состояния равен 1:

S_SPEC  POP     BC      ;Содержимое порта.
        LD      HL,(#5D1C)
        LD      DE,12   ;Восстанавливаем
        ADD     HL,DE   ;указатель
        LD      SP,HL   ;стека.
        JR      RG_D

TO_WG93 LD      IX,#2A53
TO_DOS  PUSH    IX
        JP      #3D2F

Я думаю, нет лучшего способа объяснить, как работает эта процедура, чем показать процесс её выполнения, начиная с момента вызова подпрограммы по адресу #3F33. При этом выберем именно такой случай, когда 0-й бит регистра состояния равен 1.

#3F33:  IN      A,(#1F)  ;Допустим, считано #FF.
        LD      B,A
        AND     #7F
        RET     Z        ;Не выходим.
        LD      HL,#29D8
        AND     #40      ;Проверяем 4-й бит.
        JR      NZ,#3F4B ;Условие выполняется.

#3F4B:  LD      A,#D0
        OUT     (#1F),A
        LD      A,B
        AND     #1       ;Проверяем 0-й бит.
        JP      NZ,#3EE7 ;Условие выполняется.

#3EE7:  CALL    #272B

#272B:  LD      A,#1A
        JR      #2731

#2731:  LD      (#5C3A),A
        RET

#3EEA:  LD      A,#FF
        LD      (#5D17),A
        JP      #271B

#271B:  LD      HL,#27FC
        LD      A,#06
        JP      #1C4A

#1C4A:  CALL    #03C3

#03C3:  PUSH    AF
        LD      A,(#5D0E) ;В эту ячейку
                          ;перед запуском
                          ;занесли #FE.
        CP      #FE
        JR      NZ,#03CD  ;Условие не выполняется.
        POP     AF
        RET

#1C4D:  JP      #01D3

#01D3:  LD      HL,0
        LD      (#5CF8),HL
        CALL    #20E5

#20E5:  PUSH    AF
        LD      A,(#5CF8) ;Там 0.
        CP      #FF
        JR      Z,#211C   ;Условие не выполняется.
        POP     AF
        CALL    #2970

#2970:  PUSH    HL
        PUSH    DE
        PUSH    BC

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

        PUSH    AF
        LD      HL,#5D0C  ;В эту ячейку
                          ;перед запуском
                          ;занесли #FF.
        LD      A,(HL)
        OR      A
        JR      NZ,#2992  ;Условие выполняется.

#2992:  POP     AF
        POP     BC

Содержимое BC извлечено из стека, но в памяти, отведённой под стек, оно осталось. Его можно извлечь оттуда, лишь бы оно не было затёрто записываемыми в стек значениями. Процедура обработки прерываний затрёт значение BC, а потому прерывания запрещены.

        POP     DE
        POP     HL
        RET

#20F1:  PUSH    AF
        LD      A,(#5CB6) ;В эту ячейку
                          ;перед запуском
                          ;занесли #F4.
        CP      #F4
        JR      Z,#211C   ;Условие выполняется.

#211C:  POP     AF
        RET

#01DC:  CALL    #1D63

#1D63:  LD      HL,#5D0E  ;В эту ячейку
                          ;перед запуском
                          ;занесли #FE.
        LD      A,(HL)
        CP      #FF
        LD      (HL),0
        RET     NZ        ;Условие выполняется.

#01DF:  LD      HL,#5D17
        LD      (HL),#AA
        LD      HL,#5D1F  ;В эту ячейку
                          ;перед запуском
                          ;занесли #FF.
        LD      A,(HL)
        OR      A
        LD      (HL),0
        JR      NZ,#01F3  ;Условие выполняется.

#01F3:  LD      SP,(#5D1C) ;Значения этих ячеек
        LD      HL,(#5D1A) ;подготовлены заранее.
        LD      BC,(#5D0F) ;Теперь BC осталось
                           ;только в стеке...
        LD      B,0
        JP      (HL)

При выходе управление передаётся на метку S_SPEC, а SP указывает на то самое значение регистра состояния, которое когда-то было сохранено в стеке.

Другие мои статьи о TR-DOS:

1. 

«О сокращении времени форматирования». «ZX-Ревю» 1—2/1997.

2. 

«TR-DOS: как не допустить ошибки?». «ZX-Ревю» 5—6/1997.

3. 

«Работа с диском при включённых прерываниях». Adventurer #9, «Чёрная ворона» #3, «Радиолюбитель. Ваш компьютер» 6/2000 (дополненная версия).

4. 

«Вывод трёхсимвольных расширений файлов в операционной системе TR-DOS». «Радиолюбитель. Ваш компьютер» 7/2000.

5. 

«Усовершенствованный алгоритм определения смены диска». «Радиолюбитель. Ваш компьютер» 9/2000.

6. 

«Расширения файлов TR-DOS». «Радиолюбитель. Ваш компьютер» 12/2000, «Радиомир. Ваш компьютер» 9/2001 (под псевдонимом BV_Creator).

7. 

«Обрубаем файлам „хвост“». «Радиомир. Ваш компьютер» 4/2002.

8. 

«Использование избыточной информации для защиты файлов от повреждений». «Радиомир. Ваш компьютер» 11/2002.

9. 

«Проверка корректности файловой структуры дисков TR-DOS». «Радиомир. Ваш компьютер» 6/2004.

Страница Ивана Рощина > Статьи >