Укрощение TR-DOS — Abzac


PeriodicalsAbzac2003 No. 15 → Укрощение TR-DOS

Укрощение TR-DOS

Почему я решил написать эту статью? Во-первых, я знаю, что для многих программирование под TR-DOS до сих пор остается тайной за семью печатями. Во-вторых, практически нет нормальной документации по программированию TR-DOS. В-третьих, хочу поделится с вами той информацией, теми знаниями, которые у меня накоплены. Я постараюсь представить их в виде некоего пособия с простым и понятным содержанием для людей, которые практически не разбираются в работе дисковых устройств. Впрочем, здесь будет много интересного и для разбирающихся в этом деле... Кстати, моя гордость, это процедура программного определения типа дисковода 3.5” или 5.25” (правда в этом описании ее нет). А теперь, пожалуй, самое время перейти к самому интересному.


TR-DOS


TR-DOS - дисковая операционная система разработанная в Англии. До нашей страны «докатились» две версии: 5.01 и 5.03 - датированные 1986 годом. Очевидно Technology Research Ltd было необходимо как можно быстрее «выкинуть» в свет свое творение, поскольку я не могу найти другого объяснения тoму факту, что TR-DOS написана так ужасно, напичкана всевозможными ляпами, которые не были исправлены даже в третьей редакции пятой версии! Охарактеризовывая TR-DOS, лично я нахожу лишь один плюс (как для программиста), - это последовательное хранение информации на диске, что дало возможность написания процедур дающих максимальную скорость.

Пожалуй далее идут одни недостатки. Например, по-моему, непомерно «раздута» область системных переменных, тяп-ляп написаны подпрограммы в ПЗУ, и самое главное, совершенно не предусмотрено возможности для самостоятельного низкого программирования микроконтроллера. Так же, слишком уж перестрахованные задержки в процедурах обмена данными между микроконтроллером и дисководом. Но, тем не менее, не все так уж и плохо. Используя обрывки подпрограмм в ПЗУ (ROM) TR-DOS, можно получить доступ к портам микроконтроллера.


Порты


Для управления интерфейсом BETA DISC используются следующие порты: #1f, #3f, #5f, #7f, #ff.

регистр состояния #1f [in]

регистр команд #1f [out]

in a,(#1f) - выдает состояние контроллера, где A:

https://zxpress.ru/images/periodicals/articles/original/69.gif

out (#1f),a - выполнение команды, где A - код команды (см. раздел команды).

Регистр дорожки #3f - in/out

in a,(#3f) - выдает номер текущего физического трека.

out (#3f),a - задание нового физического трека.

Регистр сектора #5f - in/out

in a,(#5f) - выдает номер текущего сектора.

out (#5f),a - задание нового сектора (нумерация с 1!).

Регистр данных #7f - in/out

in a,(#7f) - чтение байта

out (#7f),a - задание номера логического трека, для команды позиционирования или запись байта если была какая-либо команда на запись.

ini - последовательное считывание данных.

outi - последовательная запись данных.

Регистр #ff состояния сигналов intrq и drq - [in]

Системный регистр #ff - [out]

in a,(#ff) - отражает состояние сигналов intrq (d7), drq (d6) контроллера, где A:

bit

7 - intrq (сигнал окончания выполнения команды)

6 - drq (запрос данных микроконтроллером)

5 - x

4 - x

3 - x

2 - x

1 - x

out (#ff),a - вспомогательные функции, где A:

bit

7 - x

6 - метод записи (1 - fm, 0 - mfm)

5 - x

4 - выбор магнитной головки (0 - верх, 1 - низ)

3 - загрузка головки (всегда должен быть в 1)

2 - аппаратный сброс микроконтроллера (если 0)

1 - номер дисковода (00 - a, 01 - b, 10 - c, 11 - d)

0 - -/-

Порты TR-DOS доступны только тогда, когда включено ПЗУ TR-DOS! Включить ПЗУ TR-DOS можно через специальные точки входа.


Точки входа


Аппаратное включение дискового интерфейса происходит в момент выполнения какой-либо команды, записаной по адресу в диапазоне #3d00 - #3dff. Проще говоря, чтобы попасть в ПЗУ TR-DOS нужно запомнить на стеке адрес подпрограммы в ПЗУ TR-DOS, куда вы хотите передать управление и выполнить команду RET, расположенную в этом диапазоне. Либо, если адрес подпрограммы лежит непосредственно в пределах #3d00 - #3dff, можно просто передать на нее управление, например, обычным CALL NN.

Чаще всего в качестве точки входа используют две:

самая распространенная точка входа:


 #3d2f nop
       ret

Редко используемая точка входа (не «переваривается» некоторыми отечественными версиями интерфейса: GRM Pentagon и т.п.):


#3d30 ret

Но если хорошенько «покопаться» в ПЗУ TR-DOS, то можно найти еще несколько (недокументированных): #3d2c, #3d4b, #3d7f, #3d93, #3d96, #3d97, #3dac. По всем этим адресам находится команда ret, за исключением точки 3.


Недокументированная точка входа и кроме меня никем неиспользуемая:


#3d96 nop
      ret

По идее, возможно использование любого адреса в качестве точки входа из выше перечисленных, но гарантию работоспособности на всяких «левых» интерфейсах я не даю! Поэтому я настоятельно рекомендую использовать либо первую, либо третью точку входа.

Используя эти точки входа можно добраться до подпрограмм расположенных в ПЗУ TR-DOS.


Подпрограммы


С помощью обрывок подпрограмм TR-DOS можно писать свои собственные процедуры работы с диском. Для начала приведу адреса доступа к портам ввода данных:


#2fc3 out (#1f),a ;регистр команд
      ret
.....
#1e3a out (#3f),a ; регистр дорожки
      ret
.....
#1ff3 out (#ff),a ; системный регистр
      ret
.....
#2f0c out (#ff),a ; системный регистр
      ret         ; (практически не используется)
.....
#2a53 out (c),a   ; универсальный адрес
      ret

Тот же самый эффект можно получить воспользовавшись процедурой по адресу #20b8:


#20b8 out (c),d
      djnz #20b1
      ret

Или процедурой по адресу #3fd1:


#3fca in a,(#ff)    ; ожидание intrq или drq
      and %11000000 ; bit7 - intrq/bit6 - drq
      jr z,#3fca    ; для drq условие заведомо не выполнится
      ret m         ; выход по intrq
#3fd1 outi          ; запись байта
      jr #3fca

Как видите с вводом данных все просто и понятно, чего не скажешь об их получении. К сожалению в ПЗУ TR-DOS не предусмотрено такой возможности. Я не говорю о том, чтобы была такая же возможность считывать состояние каждого регистра в отдельности. В принципе, хватило бы одной простой команды типа in a,(c), но т.к. таковой нет, приходится прибегать к различного рода ухищрениям:

Так, например, чтобы прочитать значения регистров можно воспользоваться следующей процедурой по адресу #3ef3:


#3ef3 in h,(c)
#3ef5 in a,(#ff)    ; ожидание intrq или drq
      and %11000000 ; bit7 - intrq/bit6 - drq
      jr z,#3ef5    ; для drq условие заведомо не выполняется
      ei            ; !
      ret m         ; выход по intrq

На выходе в H будет значение состояния нужного вам регистра. Единственный недостаток в том, что на выходе разрешаются прерывания, что в некоторых случаях совсем не нужно.

Можно воспользоваться другой процедурой по адресу #3fec:


#3fe5 in a,(#ff)    ; ожидание intrq или drq
      and %11000000 ; bit7 - intrq/bit6 - drq
      jr z,#3fe5    ; для drq условие заведомо не выполняется
      ret m         ; выход по intrq
#3fec ini           ; чтение байта
      jr #3fe5

На выходе в (HL) будет значение состояния нужного вам регистра.

Таким образом можно считать состояние всех регистров кроме регистра состояния #1f, т.к. произойдет зацикливание при чтении #ff. Поэтому чтобы все же заполучить значение регистра состояния, нужно выполнить следующие действия:


;(c) Andrew MOA Larchenko
in_1f out (#3f),0
      out (#5f),#a
      ld d,#01
      jp #3f33

На выходе в B будет находиться значение регистра состояний #1f, т.е. таким способом мы проэмулировали команду in b,(#1f). Однако при выполнении данной процедуры были запорчены регистры дорожки и сектора, поэтому, при необходимости, следует восстановить в них исходные значения.


Команды


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


#3fe5 in a,(#ff)    ; ожидание intrq или drq
      and %11000000 ; bit7 - intrq/bit6 - drq
      jr z,#3fe5    ; для drq условие заведомо не выполнится
      ret m         ; выход по intrq

Или любой другой аналогичной процедурой по другим адресам...

Также надо отметить, что выбор дисковода и др. для выполнения команд осуществляется с помощью инструкции out (#ff),a (см. раздел порты).

Микроконтроллер может выполнять 11 команд:


1. Восстановление               - %0000hvrr - +
2. Поиск/Позиционирование       - %0001hvrr |
3. Шаг в предыдущем направлении - %001thvrr + - 
4. Шаг вперед                   - %010thvrr |
5. Шаг назад                    - %011thvrr - +
6. Чтение сектора               - %100mseca
7. Запись сектора               - %101msec0
8. Чтение адреса                - %11000e00
9. Чтение дорожки               - %11100e00
10. Запись дорожки/форматир-е   - %11110e00
11. Принудительное прерывание   - %1101iiii

где rr - скорость позиционирования головки: 00 - 6мс, 01 - 12мс, 10 - 20мс, 11 - 30мс (1Mhz).

v - проверка номера дорожки после позиционирования.

h - загрузка головки (всегда должен быть в 1!).

t - изменение номера дорожки в регистре дорожки после каждого шага.

a - тип адресной метки (0 - #fb, стирание сектора запрещено 1 - #F8, стирание сектора разрешено).

c - проверка номера стороны диска при идентификации индексной области.

e - задержка после загрузки головки на 30мс (1Mhz).

s - сторона диска (0 - верх/1 - низ).

m - мультисекторная операция.

i - условие прерывания:

i0 - после перехода сигнала cprdy из 0 в 1 (готов);

i1 - после перехода сигнала cprdy из 1 в 0 (не готов);

i2 - при поступлении индексного импульса;

i3 - немедленное прерывание команды, при:

i=0 - intrq не выдается

i=1 - intrq выдается.

Восстановление - осуществляет позиционирование головки на дорожку 0. Если через 256 импульсов сигнал tr00 не появится, то команда прекращает работу. Выполняется независимо от готовности дисковода.


to_tr0 out (#1f),#8 ; %00001000

Поиск/позиционирование - перемещает головку на нужный трек. Перемещение происходит до тех пор, пока не совпадут номера физической дорожки в #3f и логической дорожки в #7f. Номер трека указывается без учета стороны (/2)!


jump out (#7f),trk/2 ; номер нужного трека/2
     out (#1f),#18   ; %00011000
     jp #3fe5         ; ожидание выполнения команды

Шаг (пункт 3) - двигает головку на один шаг в предыдущем направлении.


step out (#1f),#38 ;%00111000
     jp #3fe5      ; ожидание выполнения команды

Шаг вперед - двигает головку на один шаг вперед.


step_f out (#1f),#58 ; %01011000
       jp #3fe5      ; ожидание выполнения команды

Шаг назад - двигает головку на один шаг назад.


step_b out (#1f),#78 ; %01111000
       jp #3fe5      ; ожидание выполнения команды

Чтение сектора - чтение сектора с текущей дорожки. При мультисекторной операции читаются все сектора с заданного до конца трека.


load_s out (#5f),sec ; номер считанного сектора (нумерация с 1)
       out (#1f),#80 ; %10000000
       ld hl,address ; адрес загрузки
       ld c,#7f
       jp #3fe5      ; чтение сектора

Запись сектора - запись сектора на текущую дорожку. При мультисекторной операции записываются все сектора с заданного до конца трека.


save_s out (#5f),sec ; номер записываемого сектора (нумерация с 1)
       out (#1f),#a0 ; %10100000
       ld hl,address ; адрес записи
       ld c,#7f
       jp #3fca      ; запись сектора

Чтение адреса - считывает 6 байтов идентификатора сектора, включая crc (контрольная сумма).


l_addr ld hl,buffer  ; адрес загрузки идентификатора
       out (#1f),#c0 ; %11000000
       jp #3fe5      ; чтение идентификатора сектора

На выходе в (HL) по смещению:

+0 - номер трека (0 - 128)

+1 - номер стороны (0-верх, 1-низ)

+2 - номер сектора (0 - 128)

+3 - длина сектора (0-128b, 1-256b, 2-512b, 3-1024b)

+4 - контрольная сумма (?)

+5 - -/-

Чтение дорожки - считывает всю дорожку целиком. Синхронизация отсутствует (crc не проверяются), т.е. нельзя отследить правильность считанной информации.


load_t ld hl,address ; адрес загрузки трека
       out (#1f),#e0 ; %11100000
       jp #3fe5      ; чтение трека (>6Kb)

Запись дорожки/форматирование - форматирование дорожки.


format ld hl,address ; адрес расположения данных дорожки
       out (#1f),#f0 ; %11110000

Принудительное прерывание - завершение выполняемой на данный момент команды. В отличие от других команд она может выдаваться в любой момент.


inter out (#1f),i ; %1101iiii (значение i см. выше)

Вот еще две подпрограммки из ПЗУ TR-DOS.



#20af ld b,#01
#20b1 in a,(#ff)    ; ожидание intrq или drq
      and %11000000 ; bit7 - intrq/bit6 - drq
      jr z,#20b1
      ret m         ; выход если intrq
#20b8 out (c),d     ; запись байта если drq
      djnz #20b1    ; повтор пока b<>0
      ret

#3ef3 in h,(c)
#3ef5 in a,(#ff)    ; ожидание intrq или drq
      and %11000000 ; bit7 - intrq/bit6 - drq
      jr z,#3ef5
      ei            ; !
      ret m         ; выход если intrq
#3efd di            ; !
#3efe in a,(#7f)    ; чтение байта если drq
      jr #3ef5      ; повтор пока не поступит intrq

Вот, собственно и все.

Адрес для связи: Юдин Александр Владимирович (faster), 432044, г. Ульяновск, ул. Героев Свири 16а-24.