8. Системные процедуры
Для программирующих на ассемблере Z80 ПЗУ (ROM) ZX Spectrum
является ценным хранилищем готовых и отлаженных процедур в машинном
коде. Некоторые из них могут с успехом использоваться с уровня
BASIC, создавая дополнительные возможности, недоступные другим
способом. Мы ограничимся описанием важнейших системных процедур.
8.1 Работа со звуком
В ZX Spectrum динамик можно возбудить двумя способами:
BEEPER #0385 (949)
- Эта процедура требует двух параметров. В регист-
ре DE помещается время продолжительности звука,
а в регистре HL - частота. Эти значения перед
помещением их в регистры требуют предварительных
преобразований. Допустим, мы хотим получить тон
с частотой f продолжительностью t. Тогда в DE
загружается f*t, а в HL загружается
437500/f-30.125. Избежать этих расчетов можно,
если вызвать другую процедуру.
BEEP #03F8 (1016)
- Она требует задания времени и частоты звука в
нормальных единицах измерения. Эти параметры
передаются BEEP путем их помещения в стек
калькулятора. BEEP снимает их оттуда
самостоятельно, выполняет необходимые
преобразования и вызывает BEEPER.
BEEPER немного лучше инструкции ZX-BASIC BEEP с той точки
зрения, что на него не распространяются ограничения на значения
параметров. Данные контролируются во время преобразований,
выполняемых BEEP. Во время генерации звука все прерывания
запрещены. Последней инструкцией BEEPER перед выполнением RET
является EL.
8.2 Работа с магнитофоном
Как заголовки, так и блоки данных записываются на кассету одной
и той же процедурой:
SAVE_BYTES #04C2 (1218)
- В регистрах DE должна находиться длина записыва-
емого блока, в IX - адрес первого байта, а в
буфере A - тип записываемого блока:
тип 0 - заголовок;
тип 255 (#FF) - блок данных.
В принципе, они отличаются тем, что заголовку
предшествует более длительный вступительный
сигнал (около 5 сек.), чем блоку данных (около 2
сек.). Пользователь может использовать и
остальные значения типа между 0 и 255 на
обозначение типа блока. Это полезно при
обозначении разных типов данных в специальных
приложениях. Такие блоки игнорируются
инструкцией LOAD при чтении (на экран не
выводятся о них никакие данные) и некоторыми
программами копирования.
Стандартный заголовок состоит всегда из 17 байт:
1 байт - тип блока данных:
0 - программа BASIC
1 - числовой массив
2 - знаковый массив
3 - набор байтов.
2-11 байты - Название длиной 10 байт. Допустимы
все коды от 0 до 255. Если название
короче 10 байт, то оно дополняется
пробелами.
12-13 байты - Длина блока, следующего за заголов-
ком.
14-17 байты - Зависит от типа набора:
Для типа 0:
14-15 байты - номер строки запуска,
отказ от запуска
сигнализируется
значением > 32767
(#7FFF).
16-17 байты - длина самой программы
без области перемен-
ных.
Для типов 1 и 2 используется только
15 байт для задания (однолитерного)
имени записываемого массива (имя,
под которым он хранился в области
переменных.
Для типа 3:
14-15 байты - начальный адрес памя-
ти, в которой
находился массив в
момент записи.
Приведенный выше формат заголовка должен сохраняться лишь для
тех случаев, когда записываемые блоки должны быть считаны
инструкцией LOAD.
Считывание записанных наборов производится инструкцией:
LOAD_BYTES #0556 (1566)
- Так же, как и для SAVE_BYTES, перед вызовом в
пару DE заносим длину считываемого блока, в IX -
начальный адрес памяти для записи блока, а в A -
тип считываемого набора. Дополнительно еще
необходимо установить указатель с инструкцией
SCF. Вызов этой процедуры с нулевым указателем C
позволяет отказаться от верификации данного
блока (аналог команды VERIFY). Возможная ошибка
считывания или верификации сигнализируется
обнулением указателя C после выхода из
подпрограммы, нормальное выполнение процедуры
сигнализируется установкой C=1.
8.3 Вывод на экран и печать.
Вывод на экран единичного символа, код которого находится в
буфере, на верхнюю и нижнюю части экрана, а также на принтер,
выполняется одной и той же процедурой. Перед ее вызовом необходимо
открыть соответствующий канал с помощью процедуры:
CHAN_OPEN #1601 (5633)
В буфере должен находиться признак необходимого канала: 1 -
для "K", 2 - для "S", 3 - для "P". Этот канал будет открыт до тех
пор, пока мы его сами не закроем. Если после этого выдать команду
ассемблера:
RST #10
то она будет печатать единичный символ, находящийся в буфере A, по
выбранному каналу.
Программа, приведенная ниже, иллюстрирует применение RST #10.
Ее выполнение равнозначно команде: PRINT FLASH 1; AT 5,3;"X";#3;"A"
LD A,2 ;открыть канал "S"
CALL CHAN_OPEN
LD A,#12 ;код символа FLASH
RST #10
LD A,1 ;аргумент FLASH
RST #10
LD A,#16 ;код символа AT
RST #10
LD A,#5 ;аргументы AT
RST #10
LD A,#3
RST #10
LD A,#58 ;код "X"
RST #10
LD A,3 ;открыть канал "P"
CALL CHAN_OPEN
LD A,#41 ;код символа "A";
RST #10
RET ;вывод из подпрограммы
Обратим внимание, что знак "A" будет послан в буфер принтера,
а фактически будет отпечатан на бумаге только после заполнения
буфера, пересылки в буфер символа конца строки (13) или вызова:
COPY_BUFF #0EC0 (3789)
Этот пример показывает, что пересылка цепочки знаков по одному
символу может быть крайне утомительна. Проще применить процедуру:
PR_STRING #203C (8252)
- Она печатает цепочку знаков, адрес которой задан
в регистре DE и длиной в BC. Перед ее вызовом
необходимо открыть соответствующий канал. Если
печатаемые символы не уточняют цвета, то они
устанавливаются на основании переменных ATTR_T,
MASK_T, а также нечетных битов P_FLAG.
Печать чисел более затруднительна, так как необходимо
осуществлять перевод двоичного числа в последовательность символов
его десятичного представления. Все необходимые вычисления и печать
выполняет процедура:
PRINT_FP #2DE3 (11747)
- Она снимает со стека калькулятора пять байтов,
считая их числами в формате, принятом в системе
ZX-BASIC, затем печатает их с учетом системных
переменных, определяющих цвет, положение и т.д.
Способы размещения чисел на стеке калькулятора
мы оговорим далее.
В случае натуральных чисел от 0 до 9999 можно использовать
более быструю процедуру:
OUT_NUM1 #1A1B (6683)
- Она печатает число, содержащееся в BC, на 4-х
полях, выводя вначале необходимое число
пробелов. ZX Spectrum использует эту процедуру
для печати номеров строк в листингах программ.
8.4 Экранная графика
Для рисования на экране у нас есть аналоги процедур PLOT, DRAW
и CIRCLE:
PLOT_SUB #22E5 (8933)
- Позволяет вывести на экран единичную точку с
координатами (X,Y). Перед вызовом необходимо
поместить X в C и Y в B.
PIXEL_ADD #22AA (8874)
- После занесения в BC значений (Y,X) и вызова
этой функции мы получаем в регистре HL адрес
байта, описывающего данную точку экрана. К тому
же в буфер заносится значение X MOD 8,
уточняющее, о каком бите данного байта идет
речь.
DRAW_1 #2477 (9335)
- Снимает со стека калькулятора числа X и Y, после
чего рисует соответствующий отрезок. Координаты
PLOT выбираются из системных переменных.
DRAW_3 #24BA (9402)
- Для рисования аналогичного отрезка этой процеду-
рой необходимо задать ABS Y -> B, ABS X-> C, SGN
Y -> D, SGN X -> E.
DRAW_ARC #2394(9108)
- Рисует фрагменты дуг. Параметры X,Y,Z для этой
процедуры должны передаваться через стек
калькулятора. Z размещается наверху стека.
CIRCLE_1 #2320 (9005)
- Рисует полную окружность с центром в (X,Y) и
радиусом Z, все параметры также должны быть
помещены в стек калькулятора.
Внимание!!! Приведенные выше процедуры модифицируют регистры HL
(см.раздел "Ошибки системы").
После вывода нужного рисунка на экран его можно вывести на
принтер. Аналогом инструкции COPY является процедура:
COPY #0EAC (3756)
- Эта процедура копирует на принтер 22 верхние
строки.
Параметры всех вышеприведенных процедур подчинены тем же
ограничениям, что и соответствующие команды в BASIC.
8.5 Очистка и перемещение экрана
CLS #0D6B (3435)
- Основная процедура очистки экрана. Ее действие
аналогично действию директивы с тем же именем.
CLS_LOWER #0D6E (3438)
- Очистка нижней части экрана. Она действует на
всей нижней части экрана независимо от ее
текущих размеров и одновременно устанавливает ее
высоту в две строки. Инициализируются также
системные переменные DF_CL и SPOSNL,
определяющие положение курсора.
CL_LINE #0E44 (3652)
- Очистка, начиная с нижнего края экрана, заданно-
го числа строк. Число строк содержится в
регистре B.
Перед вызовом этих процедур нужно убедиться, что нужные каналы
открыты. Две первые из них оставляют после выхода канал "K"
открытым. При стирании экрана цвета устанавливаются на основании
системных переменных BORDER для нижней части экрана, а также ATTR_P
и MASK_P для верхней, после чего их содержимое копируется в ATTR_T
и MASK_T.
CL_SC_ALL #0DFE (3582)
- Перемещает содержимое всего экрана (24 полные
строки) на одну строку вверх (верхняя строка
исчезает). Ее можно вызвать непосредственно из
BASIC, так как она не требует параметров.
CL_SCROLL #0E00 (3584)
- После занесения в регистр B числа строк-1 для
сдвига (не менее 2) эта процедура продвинет на
одну строку столько строк, сколько хотим, не
трогая расположенных выше. Следовательно, можно
иметь вверху экрана рисунок или текст, которые
не уничтожаются при сдвиге на одну строку.
Применяя эти процедуры надо помнить, что вверх будут
подниматься также обязательные атрибуты в нижней части экрана.
8.6 Считывание с клавиатуры
Информацию с клавиатуры выгоднее всего снимать с системной
переменной LAST_K. Проверяя состояние пятого бита переменной FLAGS
можно определить: нажата или не нажата очередная клавиша (1-нажата
новая, 0-еще не нажата ни одна с момента обнуления этого бита).
После считывания кода клавиши, обнуляя этот бит, мы обеспечиваем
себе возможность знать, модифицировалось ли еще содержимое LAST_K
или нет. Действует это только при включенных замаскированных
прерываниях в режиме 1. Именно в этом случае ZX Spectrum
автоматически вызывает процедуру 50 раз в секунду:
KEYBOARD #02BF (703)
- Она просматривает всю клавиатуру, идентифицируя
верную комбинацию клавиш, заносит считанный код
в буфер, а также в переменную LAST_K и
устанавливает 5 бит FLAGS. Интерпретация нажатой
клавиши будет зависеть от трех системных
переменных. Сначала проверяется содержимое MODE,
рассматриваемое как число со знаком в коде
дополнения до двух:
MODE-1 < 0 обозначает курсор K, L или C
MODE-1 = 0 обозначает курсор E
MODE-1 > 0 обозначает курсор G
Курсор K от L и C отличается с помощью третьего
бита переменной FLAGS. 0 означает K, а 1
сигнализирует L или C. Третий бит FLAGS
окончательно определяет, является ли курсор L(0)
или C(1). Эта процедура не ожидает нажатия
клавиши.
Чтобы независимо от курсора установить, была ли нажата
конкретная клавиша, более продуктивным может быть непосредственный
опрос клавиатуры с помощью инструкции IN. Здесь мы поступаем так
же, как и в случае функции с тем же самым названием в BASIC.
8.7 Калькулятор
Различные действия над числами с плавающей запятой выполняются
с помощью большого набора процедур, занимающих пространство ROM от
#2F9B (12187) до #386D (14445). Калькулятор вызывается инструкцией
RST #28, которая выполняет переход на адрес #335B (13457).
Основой работы калькулятора являются 66 различных процедур,
выполняющих набор базовых операций на стеке калькулятора.
Очередность их выполнения задается цепочкой байт, размещенной
непосредственно за командой RST #28. Конец такой цепочки всегда
помечается байтом со значением #38 (56).
По необходимости мы ограничимся операциями, позволяющими
выполнять различные численные расчеты. Допустим, что наверху стека
находятся числа ...Z,X,Y.
----------------------------------------------------------------
|Значение байта| | Состояние стека |
|--------------| Операция | после |
| Дес. | Шест. | | операции |
|--------------------------------------------------------------|
| 1 | #01 | Замена элементов | ... Z Y X |
| 3 | #03 | Вычитание | ... Z X-Y |
| 4 | #04 | Умножение | ... Z X*Y |
| 5 | #05 | Деление | ... Z X/Y |
| 6 | #06 | Степень | ... Z X**Y |
| 15 | #0F | Сложение | ... Z X+Y |
| 27 | #18 | Изменение знака | ... Z X -Y |
| 31 | #1F | Синус | ... Z X sin Y |
| 32 | #20 | Косинус | ... Z X cos Y |
| 33 | #21 | Тангенс | ... Z X tg Y |
| 34 | #22 | Арксинус | ... Z X asn Y |
| 35 | #23 | Арккосинус | ... Z X acs Y |
| 36 | #24 | Арктангенс | ... Z X atg Y |
| 37 | #25 | Логарифм натур. | ... Z X ln Y |
| 38 | #26 | Экспонента | ... Z X exp Y |
| 39 | #27 | Целая часть числа| ... Z X int Y |
| 40 | #28 | Корень квадратный| ... Z X sqr Y |
| 41 | #29 | Знак числа | ... Z X sgn Y |
| 42 | #2A | Абсолютная вел. | ... Z X abs Y |
| 49 | #31 | Копирование стека| ... Z X Y Y |
| 50 | #32 | N MOD M | ... Z остаток частное |
| 52 | #34 | Дописать в стек | ... Z X Y D |
| 56 | #38 | Конец расчетов | ... Z X Y |
| 58 | #3A | INT(Y+.5) | ... Z X INT(Y+.5) |
| 160 | #A0 | Дозапись 0 | ... Z X Y 0 |
| 161 | #A1 | Дозапись 1 | ... Z X Y 1 |
| 162 | #A2 | Дозапись 0.5 | ... Z X Y 0.5 |
| 163 | #A3 | Дозапись PI/2 | ... Z X Y PI/2 |
| 164 | #A4 | Дозапись 10 | ... Z X Y 10 |
----------------------------------------------------------------
Как пример использования калькулятора, рассчитаем значение
1.5*SIN(X)+X**2*COS(X*PI/2). Допустим, что значение X находится
наверху стека. Размещение за инструкцией #28 следующих байт первой
колонки вызовет:
---------------------------------------------------------
| Байт | Состояние стека |
|-------------------------------------------------------|
| #31 | X X |
| #31 | X X X |
| #31 | X X X X |
| #A3 | X X X X PI/2 |
| #04 | X X X X*PI/2 |
| #20 | X X X COS(X*PI/2) |
| #04 | X X X*COS(X*PI/2) |
| #04 | X X*X*COS(X*PI/2) |
| #01 | X*X*COS(X*PI/2) X |
| #1F | X*X*COS(X*PI/2) SIN(X) |
| #34 | Дозапись 1.3 (5 байт) |
| #F1 | |
---------------------------------------------------------
---------------------------------------------------------
| #26 | |
| #66 | |
| #66 | |
| #66 | X*X*COS(X*PI/2) SIN(X) 1.3 |
| #04 | X*X*COS(X*PI/2) 1.3*SIN(X) |
| #0F | X*X*COS(X*PI/2)+1.3*SIN(X) |
| #38 | Конец вычислений |
---------------------------------------------------------
Объяснения требует запись в стек калькулятора данных как
последовательности байт, следующих непосредственно за #34. Байты
#F1#26#66#66#66 представляют число 2*113*0.13, а не 0.13. Это
связано с интерпретацией их как числа в укороченной записи. Схема
действий следующая:
- первый байт делим на #40 (64) и как значение показателя
степени принимается: остаток от этого деления плюс #50
(если он отличен от 0) или следующий байт плюс #50 (если
остаток равен 0);
- целая часть от деления (0,1,2,3) плюс 1 определяет,
сколько затребовано байт для мантиссы. Недостающие до 5
байты заполняются нулями.
В нашем примере #F1 (241), деленное на #40 (64) дает остаток
#31 (49), а также целую часть 3. Это означает, что показателем
степени нашего числа является #31+#50=#81 и что затребовано все 4
байта мантиссы. В этой системе число 0 имеет представление
#40#B0#00, так как #0, деленный на #40 дает остаток и целую часть,
равные 0. Затем становится второй байт +#50 или #B0+#50=#00 (все
эти расчеты на отдельных байтах ведутся по модулю 256) и задается
лишь первый байт мантиссы. Остальные байты (до 5) дополняются
нулями. В свою очередь, число 10 имеет представление #40#B0#00#0A.
Символ #34 позволяет размещать числа в тексте программ на
ассемблере. Однако, часто требуется поместить в стек параметры
инструкций или значения, рассчитанные в программе. Для этого
предназначено много вспомогательных процедур. Они позволяют как
извлечение из стека значения, так и запись в него различных чисел:
STK_TO_BC #2307 (8967)
- Два следующих за собой числа (в 5-байтовом пред-
ставлении) извлекаются из стека и заносятся в
регистры B и C. Их значения должны лежать в
диапазоне от -255 до 255. Иначе осуществляется
возврат в BASIC с сообщением 8 (будет выполнена
инструкция RST #8). Эта процедура может
применяться к отрицательным числам. Их знаки
возвращаются в регистрах D и E. Числа,
извлекаемые из стека, округляются до ближайшего
целого числа.
STK_TO_A #2314 (8980)
- Процедура, аналогичная предыдущей, с тем отли-
чием, что из стека извлекается только одно число
и после округления до целого помещается в буфер.
Знак числа возвращается в регистре C.
STACK_FETCH #2BF1 (11249)
- Эта процедура извлекает из стека все число (5
байтов) и размещает его в регистрах A, E, D, C,
B.
FP_TO_BC #20A2 (11682)
- Число из стека округляется до ближайшего целого
числа и размещается в регистрах BC. Знак числа
определяется указателем Z (0 для отрицательных).
Если значение в стеке превысило максимальное
65535, то указатель C устанавливается в 1 и это
единственная реакция на ошибку (возврата в
ZX-BASIC не происходит).
SIK_STORE #2AB6 (10934)
- Эта процедура размещает наверху стека 5 байтов
из регистров A, E, D, C, B. Число 1.3
(#81#26#66#66#66) можно занести в стек
калькулятора двумя способами (обратим
внимание,что второй способ позволяет сэкономить
3 байта):
LD A,#81 RST #28
LD DE,#6626 или DEFB #34
LD BC,#6666 DEFB #F1
CALL #2AB6 DEFB #26
DEFB #66
DEFB #66
DEFB #66
DEFB #38
STACK_A #2D28 (11560)
- Значение регистра A периодически заносится в
стек в 5-байтовом представлении.
STACK_RC #2D2B (11563)
- Эта процедура аналогична предыдущей, но в стек
заносится число из регистров BC.
После завершения расчетов калькулятор в регистрах HL размещает
адрес первого из 5-ти байтов, находящихся на верхушке стека. При
работе с калькулятором необходимо заботиться о соответствующей
работе со стеком. Следует также помнить, что процедура PRINT_FP
снимает со стека печатаемое число. В конце приводим вызов из
программы в машинном коде генератора псевдослучайных чисел:
LD A,#A5
CALL STACK_A
RST #28
DEFB #2F
DEFB #1D
DEFB #38
RET
Эта программа помещает очередное псевдослучайное число в
вершину стека и модифицирует системную переменную SEED.
8.8 Дополнительные системные процедуры
SET_MIN #16B0 (5808)
- Производит глобальную очистку рабочих областей
системы BASIC, а также стека калькулятора. Она
модифицирует системные переменные, указывающие
на соответствующие области памяти. Физически
затираются только те байты, в которые заносятся
указатели конца области.
MAKE_ROOM #1655 (5717)
- Вызывая ее, следует в регистрах HL дать адрес
байта начала добавочного блока, а в BC - размер
блока. Эта процедура сама устанавливает, какие
системные переменные должны быть модифицируемы и
в случае необходимости модифицирует их.
Следовательно, она допускает вставку в
существующую программу на BASIC или в область
переменных новых операторов, переменных и т.д.
RECLAIM_2 #19E8 (8168)
- Обратная предыдущей функция. Здесь в HL заносит-
ся адрес первого байта, который необходимо
убрать, а в BC - размер стираемого блока.
Также, как и прежде, соответствующие системные
переменные будут автоматически модифицированы.
CLEAR_BUFF #0EEF (8815)
- Процедура очищает буфер принтера и модифицирует
связанные с ним системные переменные.
LINE_ADDR #196E (6510)
- Отыскание адреса строки с заданным номером в
программе на BASIC. Перед вызовом в HL заносится
номер разыскиваемой строки. На выходе в HL имеем
адрес этой строки или первой с большим номером.
Если строка с заданным номером есть, то это
сигнализируется установкой указателя Z.
FREE_MEM #1F1A (7962)
- Определение свободной памяти в области BASIC,
т.е. между STKEND и RAMTOP. Регистры HL и BC
содержат то же самое отрицательное число,
представленное в коде дополнения ло 2,
являющееся разницей между STKEND+80 и SP
(указатель стека процессора Z80).
BREAK_KEY #1F54 (8020)
- Ее вызывают для проверки одновременного нажатия
клавиш CS и BREAK. Если они нажаты, то указатель
C обнулен.