Программы, обслуживающие работу с магнитофоном.
Этот раздел ПЗУ занимает адреса с 04C2 по 09F3. Основная задача содержащихся здесь процедур - исполнение четырех команд - SAVE, LOAD, VERIFY и MERGE. Они выполняют все необходимые для этого функции:
- генерируют пилоттон (широкие полосы на экране телевизора, появляющиеся перед загрузкой/выгрузкой каждого блока программы;
- записывают и считывают "хэдер" (17-байтный заголовок, который предшествует загрузке/выгрузке каждого программного блока (program), а также блоков "машинных кодов (bytes) и блоков данных (character array или number array);
- записывают и считывают сам программный блок.
Общим в работе всех подпрограмм, обслуживающих команды SAVE,LOAD,VERIFY и MERGE является то, что адрес блока, для обработки которого они предназначены, должен быть предварительно установлен в регистровой паре IX, а длина этого блока - в регистровой паре DE. При этом аккумулятор (регистр A) содержит 0, если обрабатывается 17-байтный "хэдер", а если обрабатывается сам программный/кодовый блок или блок данных, то в аккумуляторе находится число 255 (0FFH).
Начальная точка входа в этот раздел ПЗУ находится в адресе 0605H, после чего исполнение разветвляется, в зависимости от того, с какой командой из SAVE, LOAD, VERIFY и MERGE Вы работаете.
Основные наиболее крупные блоки этого раздела такие:
0462-053E.
Подпрограмма "SA-BYTES".
Выполняет передачу байтов, содержащихся начиная с адреса, на который указывает содержимое IX, в количестве, указанном в DE, на внешний порт магнитофона. Эта же подпрограмма управляет изменением цвета бордюра при загрузке/выгрузке и звуковым динамиком.
053F-0555.
Подпрограмма "SA/LD-RET".
Небольшая подпрограмма, общая для операций SAVE и LOAD. Выполняет необходимые финишные операции после окончания загрузки/выгрузки.
0556-0604.
Подпрограмма "LD-BYTES".
Выполняет прием байтов с внешнего входного порта магнитофона при операциях LOAD,VERIFY,MERGE. Как и для программы "SA-BYTES" в паре IX находится адрес, начиная с которого располагается загружаемый (проверяемый) блок, а в паре DE - его длина.
0605-096F.
Главная рабочая область данного раздела.
Поскольку адрес 0605 является точкой входа в пакет процедур, обслуживающих работу с магнитофоном, здесь размещается основной логический блок, который и управляет прохождением исполнения поданной Вами команды.
0970-09A0.
Подпрограмма "SAVE CONTROL".
Выполняет вспомогательные действия при выгрузке.
Осуществляет связь с пользователем, например выдает сообщение "Start tape, then press any key" и ожидает нажатия клавиши.
09A1-09F3.
Это не подпрограмма. В этих адресах хранятся сами тексты сообщений (кроме сообщений об ошибках), которые могут появиться при работе с магнитофоном.
Например, хорошо знакомое сообщение, появляющееся при начале загрузки "Program:" или "Bytes:" и др.
Поскольку процедуры, связанные с обслуживанием магнитофона имеют особую важность для пользователей, мы их рассмотрим более подробно, раскрыв логику их работы и взаимосвязь. Эти данные могут пригодиться тем, кто сам организует нестандартный ввод/вывод, занимается разработкой загрузчиков, защищающих от копирования через копировщики, а также исследует возможность снятия таких защит.
Многие так или иначе работают вопросами ускорения ввода/вывода аппаратно. Поможет эта информация и тем, кто занимается разработкой или анализом копирующих программ.
На приведенных ниже блок-схемах показана взаимосвязь между отдельными процедурами, входящими в этот раздел, а далее прокомментированы эти процедуры.
Условные обозначения:
- переход, ветвление (команда Ассемблера JP);
= вызов с возвратом (команда CALL или RST).
Тем, кто не знает машинного кода или языка Ассемблера Z-80 и по этой причине будут испытывать трудности при чтении материала, очень решительно советуем приобрести наш трехтомник по машинному коду для начинающих.
SAVE_ETC: 0605-0620
Это главная точка входа в программу для всех четырех команд LOAD, SAVE, VERIFY, MERGE. Чтобы установить какая конкретно из четырех обрабатывается, в системной переменной T_ADDR (23668 DEC) выставляется соответствующее число:
SAVE - 00; LOAD - 01;
VERIFY - 02; MERGE- 03.
Процедура выполняет подготовку к выгрузке или загрузке "хэдера". При работе вызывает процедуры:
- EXPT_EXP (1C8C) - для передачи числовых параметров "хэдера" на стек калькулятора;
- SYNTAX_Z (2530) - для проверки в каком режиме работает БЕЙСИК - в режиме исполнения команды или проверки синтаксиса. В последнем случае выполняется переход вперед на процедуру SA_DATA.
SA_SPACE: 0631-0636
Резервирует место в рабочей области БЕЙСИКа для размещения "хэдера", для чего обращается к процедуре BC_SPACES(0030).
SA_BLANK: 0639-0641
Выполняет проверку параметров имени.
Вызывает процедуру STK_FETCH (2BF1) для ввода со стека калькулятора параметров имени.
Если имя пустое или длиннее десяти символов, что допустимо только для LOAD, VERIFY и MERGE, то переход на SA_NULL, если с именем все в порядке - переход на SA_NAME.
REPORT_F: 0642-0643
Если имя неправильное, вызывается ERROR_1 (0008) с кодом перехватчиком 0E, что означает "invalid file name".
SA_NULL: 0644-064A
Если имя - пустое, переход на процедуру SA_DATA. "Обрезает" имя, если оно более 10
символов.
SA_NAME: 0648-0651
Размещает имя в рабочей области БЕЙСИКа.
SA_DATA: 0652-0669
Определяет с каким типом объекта производится операция.
Если это не блок данных, т.е. либо экран, либо программа, либо машинный код, то переход на процедуру SA_SCR$ и далее.
Если это блок данных, то переход на SA_VOLD, если имеем дело с массивом данных, имеющимся в памяти, что бывает при операциях SAVE и VERIFY, или переход на SA_NEW, если обрабатываем новый блок данных.
Вызываемые процедуры:
- GET_CHAR (0018) - проверяет код первого символа для идентификации "DATA" это или нет;
- REPORT_C (1C8A) - вызывается при попытке сделать MERGE для блока данных, что конечно же невозможно. А оттуда запускается ERROR_1 (0008) с кодом перехватчиком 0B, что означает ошибку "Nonsense in BASIC";
- NEXT_CHAR(0020)- подготовка к приему нового символа.
- LOOK_VARS (28B2) - в таблице переменных разыскивает массив данных.
REPORT_2: 0670-0671
Сообщение об ошибке, если соответствующий массив данных в памяти не существует (не разыскан). - Variable not found.
SA_V_OLD: 0672-0684
Обрабатывается массив, существующий в памяти.
Вызываемые процедуры: SYNTAX_Z - см. выше.
Если идет проверка синтаксиса, то переход на SA_DATA_1.
SA_V_NEW: 0685-068E
Определяется тип данных - символьные или числовые.
SA_V_TYPE: 068F-0691
Подготавливает для выгрузки или загрузки код типа данных в качестве первого байта "хэдера".
SA_DATA_1: 0692- 069F
Здесь анализируется та часть команды загрузки/выгрузки, которая следует за оператором DATA и за именем массива. Следующим символом должна идти открывающая скобка "(". Если это не так, то возврат на SA_V_OLD, а оттуда на сообщение об ошибке REPORT_C.
Вызываемые процедуры:
- NEXT_CHAR(0020) - см. выше.
- CHECK_END(1BEE) - обеспечивает выдачу сообщения об ошибке, если вся строка до конца не прошла успешную проверку на синтаксис.
SA-SCR: 06A0-06C2
Проверяет, является ли обрабатываемый блок экраном. Если нет, то переход на SA_CODE и далее, при попытке сделать MERGE для экрана выдает сообщение об ошибке REPORT-C. Если все в порядке, подготавливает регистры процессора к работе с экранной областью и переходит на SA_TYPE-3.
SA CODE: 06C3-06E0
Проверяет, является ли обрабатываемый блок блоком машинных кодов. Если нет, то очевидно это БЕЙСИК (больше ничего не остается) и тогда переход на SA_LINE. При попытке сделать MERGE выполняет переход на REPORT_C. Если идет операция SAVE, то проверяется есть ли параметры начала блока и длины. Выгружать коды только по имени нельзя. В этом случае также переход на REPORT_C.
Вызываемые процедуры:
- NEXT_CHAR - см. выше;
- PR_ST_END (2048) - проверяет закончен ли оператор. Если да, то возвращает 0 и работа продолжается, если нет, то строка не закончена. ПЗУ находится не в режиме исполнения, а в режиме проверки синтаксиса и следует переход на процедуру SA_CODE_1.
- USE_ZERO (1CE5) -помещает 0 на стек калькулятора в качестве адреса начала блока.
Работа продолжается переходом на SA_CODE_2.
SA_CODE_1: 06E1-06EF
Анализирует синтаксис, дает переход на REPORTS при попытке сделать SAVE без указания начального адреса и длины блока.
Вызываемые процедуры:
- EXPT_1NUM (1C82) - процедура калькулятора. Рассчитывает значение последнего выражения. Результат становится последним вложением на вершине стека калькулятора, которое далее считается адресом начала обрабатываемого блока кодов.
- GET_CHAR (0018) - см. выше. Продолжение работы - SA_CODE_3.
SA_CODE_2: 06F0-06F4
Посредством вызова процедуры USE_ZERO (1CE6) помещает 0 на вершину стека калькулятора в качестве параметра длины обрабатываемого блока машинного кода. Продолжение работы - SA_CODE_4.
SA_CODE_3: 06F5-06F8
С помощью процедуры калькулятора EXPT-1NUM рассчитывает значение последнего выражения, помещает его на вершину стека и считает его длиной блока кодов.
Начиная с этого времени все параметры хранятся в той части рабочей области БЕЙСИКа, которая отведена для хранения "хэдера".
SA_CODE_4: 06F9-0709
С помощью процедуры калькулятора FIND_INT2 (1E99) преобразует параметр "длина" блока из интегральной формы, применяющейся в расчетах калькулятора в двухбайтную, приемлемую для машинного кодирования. Потом то же самое для параметра "начальный адрес".
SA_TYPE_3: 0710-0715
Подготавливает для выгрузки или загрузки код типа данных в качестве первого байта "хэдера". В данном случае и для экрана и для блока маш. кодов этот тип - 3.
SA-LINE: 0716-0722
Проверяет присутствует ли оператор LINE в командной строке. Если да, то переход на SA_LINE_1. Туда же переход в режиме проверки синтаксиса, если по результатам вызова CHECK_END (1BEE) установлено, что строка не завершена.
Продолжение работы - переходом на SA_TYPE_0.
SA_LINE_1: 0723-0739
Если не в режиме SAVE, то оператору LINE здесь делать нечего и выполняется переход на REPORT_C.
Принимается число, стоящее после LINE, помещается на вершину стека калькулятора, переводится в двухбайтную форму и запоминается.
EXPTEXP SYNTAX Z
BC SPACES
STK FETCH
REPORT F
SA NULL
GET CHAR
SANAME
NEXTCHAR LOOKVARS
REPORT C
CHECK END
EXPT1NUM CHECK END
FIND INT2
REPORT2
► REPORTC
► REPORT C
SACODE2
SA V TYPE
NEXT CHAR
SACODE3
CHECK END
CHECK END
FIND INT2
» SA TYPE 3
SABYTES
SA_TYPE_0: 073A-0759
Записывает в рабочую область БЕЙСИКа, отведенную для "хэдера" информацию о программе и ее переменных:
тип (в данном случае он равен нулю), а также содержимое системных переменных PROG и E-LINE.
Итак, в результате этих операций в рабочей области БЕЙСИКа сформировалась область информации по "хэдеру" загружаемого или выгружаемого блока. На начало этой области указывает число, находящееся в регистровой паре IX процессора, а далее информация распределена следующим образом:
IX+00 - тип (данные, коды, Бейсик).
!Х+01 - IX+0A - имя (десять символов). Если имени нет, то по адресу IX+01 должно быть число FF.
IX+0B - IX+0C - количество байтов в блоке данных.
IX+0D - IX+10 - прочая информация, зависящая от типа.
SA_ALL: 075A-0766
Здесь программа начинает вводить различия для разных операций. Для операции SAVE управление передается на SA_CONTRL, а для остальных операций продолжается исполнение процедурой LD_LOOK_H.
Напоминаем, что различает программа о какой операции идет речь по содержимому системной переменной T-ADDR(23668-десятиричный адрес).
LD_LOOK_H: 0767-0789
Процедура выполняет поиск заголовка ("хэдера") на ленте и печать его на экране. Поиск организован в цикле, пока "хэдер" не будет найден.
Если заголовок найден, но его тип не совпадает с ожидаемым - переход на LD_TYPE. Если совпадает, то дается команда на сравнение десяти символов имени и выполняется прямой проход на LD_TYPE.
Вызываемые процедуры: - LD_BYTES (0556) - выполняет загрузку встреченного заголовка. Подробнее об этой подпрограмме ниже.
CHAN_OPEN (1601) - открывает канал "S" для печати имени на экране.
LD_TYPE: 078A-07A5
Печатает на экране сообщение Program или Bytes, Number array, Character array. Печать выполняется вызовом предназначенной для этого процедуры PO_MSG(0C0A).
Если в "хэдере" прочитан несуществующий тип - 4 и более, возврат на LD_LOOK_H.
Если принятое имя надо сравнить с имеющимся в компьютере, то переход на LD_NAME, но если в памяти содержится пустое имя, то прямой проход туда же с указанием, что сравнивать их не надо.
LD_NAME: 07A6-07AC
Сравнивает имена символ за символом и переходит на LD_CH_PR. Если имена сравнивать не надо, сразу выполняется переход туда же.
LD_CH_PR: 07AD-07CA
Печатает символ за символом, используя для этого вызываемую процедуру PRINT_A_1 (0010). После печати очередного символа возвращается за новым в LD_NAME.
Если в имени оказалось менее 10 символов, то это не имя и тогда выполняется возврат в процедуру LD_LOOK_H для поиска на ленте другого блока.
Далее исполнение разветвляется в зависимости от того, с какой из загрузочных команд мы имеем дело.
VERIFY - VR_CONTRL(07CB)
LOAD - LD_CONTRL(0808)
MERGE - ME_CONTROL(08B6).
VR_CONTRL: 07CB-07E8
Процедура принимает длину загружаемого блока, сравнивает ее с заказанной и переходит на VR_CONT_1. Если длина не была указана при подаче команды, то сразу переходит туда же. Если длина читаемого блока не совпадает с заказанной длиной, переход на REPORT_R (0806).
VR_CONT_1: 07E9-07F3
Выставляет адрес начала загружаемого (проверяемого блока) и переходит на VR_CONT_2. Если при подаче команды этот параметр был опущен, то принимается тот, который был прочитан в заголовке загружаемого блока и сразу выполняется переход туда же.
VR_CONT_2: 07F4-07FF
Принципиально верификация мало чем отличается от загрузки. Просто при верификации байт считанный с магнитфонного порта не запоминается в памяти, а только сравнивается с тем, который там находится. Поэтому значительная часть процедур, обрабатывающих режим VERIFY используется также и в режиме LOAD, если речь идет о загрузке блоков маш. кодов (Bytes) или экранов (Screen$), но только не БЕЙСИКа.
Различие начинается здесь, в этой процедуре.
Процедура выставляет флаг C, если идет режим LOAD и снимает его, если режим VERIFY.
VR_CONT_3: 0800-0801
Выставляет FF в аккумуляторе (подготовительная операция перед загрузкой блока).
LD_BLOCK: 0802-0605
Вызывает процедуру LD_BYTES(0556), выполняющую загрузку. О ней мы поговорим ниже. Если загрузка прошла нормально (а проверяют это проверкой флага C) - "выход". Если нет, переход на REPORT_R.
REPORT_R: 0806-0807
При появлении ошибки вызывает процедуру ERROR_1(0008) с кодом перехвата 1A, что означает Tape loading error.
LD_CONTRL: 0808-0818
Отсюда начинается управление загрузкой БЕЙСИКа.
Возможны два варианта - загружается БЕЙСИК-программа или именованный массив данных.
Напомним, что управлением загрузкой прочих типов занималась ветвь программы, предназначенная для верификации - VR_CONTRL и др.
Из "хэдера" принимается длина загружаемого массива. Если она не указана - переход на LD_CONT_1, а если указана - на LD_CONT_2.
LD_CONT_1: 0819-0824
Проверяет, достаточно ли в существующей рабочей области БЕЙСИКа места под загружаемую переменную или массив. Если достаточно, то переход на LD_DATA(082E).
Если нет - продолжение работы.
LD_CONT_2: 0825-082D
Резервирует под переменную дополнительно 5 байтов и проверяет хватает ли на это
оперативной памяти. Проверка выполняется вызовом TEST-ROOM(1F05).
LD-DATA: 082E-084B
Здесь рассматривается загрузка массива.
Если идет загрузка БЕЙСИК-программы, то сразу переход на процедуру LD_PROG.
Если загружается новый массив, то сразу делается переход на процедуру LD_DATA_1.
Если такой массив уже существует, резервируется дополнительное место под описание массива и работа продолжается.
Вызываемая процедура:
- RECLAIM_2 (19E8) - выполняет резервирование памяти.
LD_DATA_1: 084E-0872
Отводит место под содержимое элементов массива в конце области переменных БЕЙСИКа.
Резервирование места в этой области выполняет вызываемая процедура MAKE_ROOM (1655).
Далее производится сама загрузка блока данных (массива). Это выполняется переходом на процедуру LD_BLOCK (0802), которую мы уже рассмотрели.
LD_PROG: 0873-08AC
Отсюда начинаются операции по загрузке БЕЙСИК-программы вместе с переменными.
Сначала с помощью процедур RECLAIM_1 (19E5) и MAKE_ROOM(1655) отводится место в памяти. Выставляются некоторые системные переменные. Если программа загружается без номера строки автостарта, то выполняется переход на LD_PROG_1, а если с номером, то выставляются соответствующие системные переменные и потом делается переход туда же.
LD_PROG_1: 08AD-08B5
"Оформляет" БЕЙСИК-программу как блок данных, выставляя соответствующие значения в регистрах процессора и переходит для загрузки на LD_BLOCK(0802) и далее, о чем мы уже говорили.
ME_CONTRL: 08B6-08D1
Отсюда начинается управление исполнением команды MERGE.
Работа происходит в три приема. Сначала подзагружаемые БЕЙСИК-строки загружаются как блок данных, затем производится слияние БЕЙСИК-строк, а затем сливаются новые переменные.
Процедура ME_CONTRL сначала принимает параметры загружаемого блока и резервирует в рабочей области необходимое место посредством вызова BC_SPACES(0030), затем подготавливает загрузку блока и выполняет ее вызовом LD_BLOCK (см. выше).
ME_NEW_LP: 08D2-08D6
Это начало внешнего цикла, в котором один за другим вводятся номера строк новой программы и проверяются. Когда все строки просмотрены, выполняется переход на ME_VAR_LP(08F0).
ME_OLD_LP: 08D7-08DE
Это начало внутреннего цикла, в котором рассматриваются строки "старой" программы. Проверяется и сравнивается с номером "новой" строки сначала старший байт номера. Если они не совпадают, то сразу переход к следующей процедуре, в противном случае проверяется еще и младший байт.
ME_OLD_L1: 08DF-08EA
Когда по результатам сравнения включится флаг CARRY регистра F это означает, что подходящее место для вставки "новой" строки найдено и выполняется переход на ME_NEW_L2.
В противном случае отыскивается с помощью вызываемой процедуры NEXT_ONE адрес, в котором начинается следующая строка "старой" программы и выполняется возврат к началу цикла (ME_OLD_LP) для рассмотрения очередной строки "старой" программы.
ME_NEW_L2: 08EB-08EF
Вызовом процедуры ME_ENTER (см. ниже) выполняется вставка "новой" строки между строками "старой" программы и происходит переход в начало внешнего цикла - к процедуре ME_NEW_LP.
ME_VAR_LP: 08F0-08F8
Примерно так же, как выше происходило слияние строк, здесь начинается слияние переменных.
Это начало внешнего цикла. Сначала производится проверка на конец работы. Если все имена переменных обработаны - "ВЫХОД". В противном случае продолжение работы.
ME_OLD_VP: 08F9-0901
Это начало внутреннего цикла, в котором сканируется область переменных "старой" программы и просматриваются их имена.
Если найден маркер конца области, то выполняется переход на ME_VAR_L2.
Имена переменных старой и новой программы сравниваются сначала по первой букве. Если они совпадают, то сразу делается переход на ME_OLD_V2, где их рассмотрят более подробно.
ME_OLD_V1: 0901-0908
Если имена не совпадают, то с помощью вызываемой процедуры NEXT_ONE (19B8) устанавливается адрес следующей "старой" переменной и происходит возврат к началу внутреннего цикла ME_OLD_VP.
ME_OLD_V2: 0909-0911
Если имена состоят из одной буквы, а они совпадают, то выполняется переход на ME_VAR_L1, если же имя "длинное", то с переменной займется следующая процедура.
SALDRET
ME_OLD_V3: 0912-091D
Сравниваются вторые и последующие буквы имен, для чего в процедуре организован внутренний цикл. Если какая-то из букв не совпадает, то выполняется переход на ME_OLD_V4.
Если установлено, что имена совпали, то выполняется переход вперед на ME_VAR_L1.
SABYTES |
1 |
|
SAFLAG |
|
|
1 |
|
SALEADER |
1 |
г |
SASYNC1 |
1 |
Г |
SASYNC2 |
1 |
|
SALOOP |
1 |
|
SALOOPP |
1 |
|
SASTART |
1 |
г |
SAPARITY |
|
SABIT2 |
1 |
г |
SABIT1 |
1 |
г |
SASET |
1 |
г |
SAOUT |
1 |
г |
SA8 |
BITS |
1 |
г |
выход |
1 |
|
SALDRET |
ME_OLD_V4: 091E-0920
Если имена в предыдущей процедуре не совпали, то отсюда выполняется возврат в начало внутреннего цикла для выбора следующей переменной - ME_OLD_V1.
ME_VAR_L1: 0921-0922
Сюда Вы попадаете, если переменные совпали (по имени). Здесь в регистр A засылается код FF, что в дальнейшем будет означать - "заменить содержимое".
ME_VAR_L2: 09E3-092B
Сюда Вы попадаете, если достигнут конец области переменных и есть переменные, которым не нашлось пары. В результате регистр A содержит код + 80, что означает "должны быть добавлены переменные"._
LD BYTES
» LD BREAK
LD EDGE 1
LD EDGE 2
LDEDGE2
LD EDGE 1
► LD LOOP <
► SA/LDRET
LD VERIFY
SA/LDRET
LD NEXT
* LD MAKER
LD EDGE 2
LD_8_BITS |
II |
|
|
|
|
ВЫХОД |
|
SA/LD_RET |
|
|
ВЫХОД
"> SA/LD RET
Процедура вызывает подпрограмму ME_ENTER для добавления новой переменной и возвращается к началу внешнего цикла ME_VAR_LP.
ME_ENTER: 09EC-093D
С этой процедуры начинается пакет, предназначенный для "сливания" строк БЕЙСИКа и программных переменных. Регистры процессора и флаги несут в себе информацию, необходимую для выполнения этих операций:
Флаг CARRY (C)
Выключен - слияние БЕЙСИК-строки. Включен - слияние переменной.
Флаг ZERO (Z)
Выключен - добавление. Включен - замена.
Регистровая пара HL
Указывает на адрес начала новой строки (переменной).
Регистровая пара DE
Указывает на адрес ее места назначения.
Процедура ME-ENTER проверяет флаг Z и если речь идет о добавлении, а не о замене, сразу передает управление на ME_ENT_1.
Вызываемыми процедурами NEXT_ONE и RECLAIM_2 (см. выше) запрашивает и заносит в регистры информацию об адресе старой переменной (строки).
ME_ENT_1: 093E-0954
Проводит подготовительные операции, например резервирует место для новой строки. Переходит на ME_ENT_2, если добавляется переменная и на ME_ENT_3, если строка.
ME_ENT_2: 0955-0957
Резервирует место для новой переменной.
ME_ENT_3: 0958-096F
Выполняет добавление/замену. Удаляет из рабочей области копии "новых" строк и переменных. Переустанавливает системную переменную PROG и заканчивает работу, выполняя возврат в вызывающую процедуру.
SA_CONTRL: 0970-0990
Мы возвращаемся к операции выгрузки - SAVE. Данная процедура служит для управления процессом выгрузки.
Сначала выполняется вызов CHAN_OPEN(1601), что открывает канал "K", который служит для вывода информации в нижние две строки экрана.
Затем вызовом PO_MSG(0C0A) выполняется печать в этих строках системного сообщения Start tape and press any key.
Далее процедура WAIT_KEY(15D4) организует ожидание нажатия клавиши.
И, наконец, вызовом SA_BYTES, о чем мы еще поговорим ниже выполняется выгрузка "хэдера" на ленту. Он выгружается таким образом, что первый байт указывает на тип выгружаемого блока, а последний байт является байтом чётности.
SA_1_SEC: 0991-09A0
Выдерживает одну секунду и, после подготовки, выполняет переход на SA_BYTES для выгрузки самого блока.
09A1 - 09F3 - сообщения.
Здесь хранятся тексты системных сообщений, выдаваемых на экран во время работы с магнитофоном. Последний байт каждого сообщения инвертирован (к нему прибавлено число +80H).
09A2 - Start tape, then press any key.
09C1 - # Program:
09CB - # Number array:
09DA - # Character array:
09EC - # Bytes:
Мы здесь употребили знак # вместо символа "возврат каретки" CHR$ 13.
Теперь мы рассмотрим вызываемые процедуры, выполняющие выгрузку байтов -SA_BYTES и их загрузку - LD_BYTES.
SA_BYTES: 04C2-04CF
Сначала процедура выставляет на машинном стеке (не путать со стеком калькулятора) адрес 055F. Это означает, что по окончании операции, при выходе из нее по команде RET, будет сделан переход по этому адресу, где расположены финишные процедуры, связанные с окончанием загрузки/выгрузки SA/LD_RET и другие.
Устанавливаются постоянные, обеспечивающие продолжительность пилоттона перед "хэдером" продолжительностью 5 сек (в HL устанавливается число 1F80). Если SA_BYTES была вызвана для выгрузки "хэдера", то отсюда сразу следует переход на процедуру SA_FLAG. Если для выгрузки блока, то выставляется продолжительность пилоттона примерно 2 сек (в HL число 0C96).
SA_FLAG: 04D0-04D7
Отключает маскированное прерывание, загружает в аккумулятор 02, что означает цвет бордюра - красный и состояние динамика - включен.
SA_LEADER: 04D8-04E9
Здесь организуются выдача импульсов, формирующих сигнал пилоттона. Это сделано введением трех циклов. Продолжительность каждого импульса (включен/выключен) - 2048 тактов процессора - первый цикл, выполненный на базе команды DJNZ. Количество фронтов импульсов определяется содержимым регистровой пары HL. После каждого прохода L уменьшается на единицу - это второй цикл на базе команды JR NZ. Если L упадет до нуля, то снимается единица из H, что вызывает появление третьей ветви возврата к началу процедуры по команде JP P. Движение по третьей ветви конечно более длинное, чем по второй, поэтому в ней вводится компенсация DEC B.
Мы специально так подробно комментируем эту очень короткую процедуру, с которой Вы теперь разберетесь даже имея минимальные навыки по машинным кодам, "прощелкав" командой PEEK содержимое указанных адресов (если нет под рукой дисассемблера). Дело в том, что создание собственного пилоттона, отличного от стандартного - излюбленный прием тех, кто делает процедуры, защищенные от копирования. Впрочем, это только один из множества приемов.
SA_SYNC_1: 04EA-04F1
Вырабатывается синхронизирующий импульс. На входе в процедуру стоит задержка, обеспечивающая отключение динамика в течение 667 тактов, при этом цвет бордюра -красный, затем командой OUT по порту FE выдается сигнал на динамик и переустанавливается содержимое аккумулятора для изменения цвета бордюра на голубой.
SA_SYNC_A: 04FE-04FD
Окончание формирования синхроимпульса. На входе в процедуру стоит задержка, обеспечивающая включенное состояние динамика в течение 735 тактов. Затем командой OUT (FE),A выключается динамик и включается голубой цвет бордюра (в аккумуляторе - 0D).
Тем, кто не знает как производится управление внешним портом FE при вводе и выводе, мы можем только порекомендовать заглянуть на стр. 89-91 нашей книги "Первые шаги в машинных кодах" (т.1 трехтомника). Там все очень популярно написано. Мы не случайно называем этот трехтомник базовым - мы будем ссылаться на него еще не раз.
Далее управление передается процедуре SA_START.
SA_LOOP: 04FE-0504
Процедура является вершиной цикла, связанного с выгрузкой пакета байтов.
Проверяется не достиг ли нуля счетчик выгружаемых байтов (пара DE). Если да, то перед концом работы надо выдать байт четности, для чего переход на SA_PARITY. Если нет -продолжить работу.
SA_LOOP_P: 0505-0506
Фиксируется текущее значение четности.
SA_STAST: 0507-050D
Подготавливается побитная выдача на порт магнитофона восьми битов, составляющих байт. Включается флаг переноса (CARRY), служащий маркером для 8-ми битов байта. По нему процедура SA_8_BITS будет распознавать, что все 8 битов отправлены. В аккумуляторе выставляется 01 (бордюр красный, динамик включен).
Управление передается процедуре SA_8_BITS.
SA_PARITY: 050E-0510
Сюда Вы попадаете, если блок фактически выгружен и осталось отправить завершающий байт четности. Он вводится в регистр H и управление передается назад в SA_LOOP_P для последнего прохода.
SA_BIT_2: 0511-0513
Каждый бит выгружается после двух проходов по нижележащей петле. На первом проходе вырабатывается как бы передний фронт импульса, на втором - задний. Процедура SA_BIT_2 - вершина второго прохода. Здесь устанавливается желтый цвет бордюра и сигнал на отключение динамика.
SA_BIT_1: 0514-0519
Вершина петли первого прохода. Здесь расположен замедляющий цикл, обеспечивающий задержку на время 801 такта процессора. Далее если выгружаемый бит равен нулю, то переход на SA_OUT.
SA_SET: 051A-051B
Если выгружаемый бит равен единице, то здесь выполняется принудительная задержка еще на 855 тактов в дополнение к имевшимся ранее 801. Поэтому частота следования импульсов нулей в "Спектруме" примерно в два раза выше частоты единиц.
SA_OUT: 051C-0524
Выдает на порт FE при первом проходе 01 (динамик включен, бордюр-синий), а при втором проходе 0E (динамик выключен, бордюр желтый). После первого прохода переход на SA_BIT_2, после второго - на SA_8_BITS.
SA_8_BITS: 0525-053B
Организует побитную выдачу восьми битов байта. Отправляет бит за битом в SA_BIT_1, пока не достигнут ранее установленный маркер.
Путем опроса внешнего порта клавиатуры (тоже FE) проверяет нажатие клавиши BREAK и в этом случае выполняет выход и, соответственно, переход по содержимому машинного стека на SA/LD_RET.
Проверяет счетчик выданных байтов (DE) и, если он не достиг FFFF, обращается назад к процедуре SA_LOOP. Обращаем внимание, что когда счетчик равен нулю, то нужен еще один проход для выдачи байта четности, отсюда и взялось число FFFF, которое в двоичной дополнительной арифметике тождественно минус единице.
SA_DELAY: 053C-053D
Короткая задержка перед выходом.
Теперь рассмотрим работу пакета процедур, непосредственно занимающихся
загрузкой байтов.
При входе сюда аккумулятор содержит 00, если загружается "хэдер" или FF, если
блок.
Флаг C включен при операциях загрузки и выключен при верификации.
LD_BYTES:0556-056A
Процедура вызывается как при загрузке информации из "хэдера", так и при загрузке/верификации самого блока.
Выполняет подготовительные операции, отключает маскируемое прерывание, устанавливает цвет бордюра белым (0F на порт FE). Устанавливает на машинный стек адрес возврата 053F (SA/LD_RET), считывает байт, поступивший с порта FE, но рассматривает только бит EAR (бит 6) путем ротации RRA и маскирования AND 20, подготавливает переключение бордюра на красный, включает флаг нуля (Z).
LD_BREAK: 056B
Проверяет факт нажатия клавиши BREAK по состоянию флага Z. Если он выключен, выход на SA/LD_RET.
LD_START: 056с-0573
Вызывает процедуру LD_EDGE_1 для поиска фронта сигнала. Если в течение заданного времени он не отыскивается (свидетельствует об этом выключенный флаг переноса), то выполняется возврат на LD_BREAK. Если найден, бордюр становится голубым.
LD_WAIT: 0574-057F
Чтобы убедиться, что сигнал не случайный, здесь выполняется задержка примерно на 1 сек. И повторяется попытка найти фронт вызовом LD_EDGE_2. Если его нет, то возврат на LD_BREAK.
LD_LEADER: 0580-058E
Вызывает LD_EDGE_2 для поиска фронта сигнала. Если он не поступил в отведенное время, возврат на LD_BREAK. Если поступил, то по содержимому регистра B определяется время, прошедшее с момента приема предыдущего фронта. Если оно больше 3000 тактов, то выполняется возврат на LD_START.
В регистре H подсчитывается количество пар фронтов. Только когда их насчитывается 256 пилоттон считается принятым и - работа может быть продолжена.
LD_SYNC: 058F-05A8
За пилоттоном идут две части синхроимпульса. Любой принятый фронт считается за передний фронт импульса "on", но только если в заданном интервале времени поступит еще один фронт, он будет принят за задний, иначе возврат на LD_BREAK.
Теперь ожидается импульс "off". Если он не поступил - выход на SA/LD_RET с выключенным флагом переноса (ошибка ввода).
Теперь здесь перед загрузкой "хэдера" или блока данных подготавливаются регистры к приему первого байта, свидетельствующего о типе данных и последнего байта (байта четности). Работа продолжается переходом на LD_MARKER.
LD_LOOP: 05A9-05B2
Вершина цикла загрузки байтов. Отсюда управление распределяется в следующем порядке:
При загрузке первого (флагового) байта - на LD_FLAG.
При верификации - на LD_VERIFY.
При загрузке последующих байтов - на LD_NEXT.
LD_FLAG: 05B3-05BC
Здесь проверяется первый загруженный байт, определяющий тип загружаемых данных. Если он не совпадает с тем, что было указано в команде, выполняется выход на SA/LD_RET. В противном случае - продолжение работы - переход на LD_DEC.
LD_VERIFY: 05BD-05C1
В аккумулятор вводится текущий байт из памяти. В регистре L - байт, принятый с ленты. Если они не совпадают - выход (SA/LD_RET).
LD_NEXT:05C2-05C3
Переход к очередной ячейке памяти.
LD_DEC: 05C4-05C7
Уменьшает на единицу счетчик загружаемых, байтов, вводит в регистр B постоянную времени, сравнением с которой впоследствии определяют что было загружено - ноль или единица.
LD_MARKER: 05C8-05C9
Очищает регистр L, в котором будет "собираться" из битов загружаемый байт и включает в нем младший бит. После приема 8-ми битов он смещаясь влево отойдет в позицию флага C и это будет свидетельствовать о том, что байт принят полностью. Таким образом он выполняет роль маркера.
LD_S_BITS: 05CA-05E2
Организуется цикл по загрузке 8-ми битов от младшего до старшего. Цикл заканчивается, когда включится флаг C.
Загрузка битов выполняется вызовом LD_EDGE_2. Там "ловится" пара фронтов, замеряется интервал между ними. Сравнивается с величиной, установленной в B. Если больше, то принята единица, а если меньше, то ноль.
В регистре H фиксируется текущее значение четности.
По содержимому DE определяется весь ли блок обработан и если да, то выход на SA/LD_RET, а если нет, то возврат в вершину цикла - в процедуру LD_LOOP.
Далее следуют процедуры, занимающиеся отысканием на ленте фронтов импульсов и определением временного интервала между ними, по которому и судят о том, что это - ноль, единица, пилоттон или вообще случайный импульс.
LD_EDGE_2: 05E3 - 05E6
Служит для отыскания пары фронтов, первого путем вызова LD_EDGE_1, а второго путём прямого прохода туда же.
LD_EDGE_1: 05E7-05E8
Устанавливает в аккумуляторе величину первичной задержки.
LD_DELAY: 05E9-05EC
Выполняет эту задержку в течение 358 тактов перед входом в сравнивающую процедуру.
LD_SAMPLE: 05ED-0604
Организуется цикл, в котором с порта FE "ловится" фронт импульса. Регистр B выполняет роль счетчика проходов (таймера). Если он достиг нуля, а фронта нет - это означает "время истекло" и выполняется возврат с включенным флагом нуля (Z). Одновременно по сигналу с этого порта определяет факт нажатия BREAK-клавиши и выходит с выключенными флагами переноса (C) и нуля, если она нажата.
Если в отведенное время фронт найден, то меняет цвет бордюра и состояние звукового динамика, и производится возврат в вызывающую процедуру.
Нам осталось только рассмотреть несколько финишных процедур, занимающихся оформлением окончания процессов загрузки/ выгрузки/ верификации/ слияния.
SA/LD_RET:053F-0551
Восстанавливает исходный цвет бордюра, в последний раз проверяет нажатие клавиши BREAK, разрешает маскируемое прерывание. Если BREAK не нажата, о чем свидетельствует включенный флаг C, то переходит на SA/LD_END.
REPORTED: 0552-0553
Если нажата BREAK-клавиша, вызывается процедура обработки ошибок ERROR_1 (0008) с кодом перехватчиком 0C, что означает выдачу сообщения: BREAK-CONT repeats.
SA/LD_END: 0554-0555
Восстанавливает со стека флаги и возвращает в вызывающую программу.