Глава 8. С И С Т Е М Н Ы Е П Р О Ц Е Д У Р Ы Для программирующих на ассемблере Z80, ПЗУ (ROM) ZX-SPECTRUM является ценным хра- нилищем готовых и отлаженных процедур в машинном коде. Некоторые из них могут с успехом использоваться с уровня Бейсика, создавая дополнительные возможности, не- доступные другим способом. Мы ограничимся описанием важнейших системных процедур. 8. 1 Работа со звуком В ZX-SPECTRUM динамик можно возбудить двумя способами. BEEPER #03B5 (949) Эта процедура тре- бует 2 параметра. В регистре DE помещается время продолжительности звука, а в регист- ре HL - частота. Эти значения, перед поме- щением их в регистры, требуют предвари- тельных преобразований. Допустим, мы хо- тим получить тон с частотой F и продолжи- тельностью T. Тогда в DE загружается F*T, a в HL 437500/F-30, 125. Избежать этих расчетов можно вызывая другую процедуру. BEEP #03F8 (1016) Она требует задания времени и частоты звука в нормальных еди- ницах измерения. Эти параметры передаются BEEP путем помещения их в стек калькулято- ра. BEEP снимает их оттуда самостоятельно, выполняет необходи- мые преобразования и вызывает BEEPER. BEEPER немного лучше инструкции ZX-Бей- сика 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 - программа Бейсика; 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 - тип считываемого на- бора. Дополнительно еще необходимо устано- вить указатель C инструкцией 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; AT5, 3;" X"; #3; "A". LD A,2; открыть CALL CHAN_OPEN; канал "S" 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; открыть CALL CHAN_OPEN; канал "P" LD A,#41; код символа "A" RST #10; RET; выход из подпрог- раммы Обратим внимание, что знак "A" будет пос- лан в буфер принтера, а фактически будет отпечатан на бумаге только после заполне- ния буфера, пересылки в буфер символа кон- ца строки (13) или вызова: COPY_BUFF #0ECD (3789). Этот пример показывает, что пересылка цепочки знаков по одному символу может быть крайне утомительной. Проще применить процедуру: PR_STRING #203C (8252). Она печатает цепочку знаков, адрес ко- торой задан в регистрах DE, длина в BC. Перед ее вызовом необходимо открыть соот- ветствующий канал. Если печатаемые символы не уточняют цвета, то они устанавливаются на основании переменных FTTR_T, MASK_T, а также нечетных битов P_FLAG. Печать чисел более затруднительна, так как необходимо осуществлять перевод двоич- ного числа в последовательность символов его десятичного представления. Все необхо- димые вычисления и печать выполняет проце- дура: PRINT_FP #2DE3 (11747). Она снимает со стека калькулятора 5 байтов, считая их числами в формате, при- нятом в системе ZX-Бейсик. Затем печатает их с учетом системных переменных опреде- ляющих цвет, положение и т. д.. Способы размещения чисел на стеке мы оговорим ни- же. В случае натуральных чисел от 0 до 9999 можно использовать более быструю процеду- ру: OUT_NUM1 #1A18 (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. Все параметры также должны быть помещены в стек калькулятора. ВНИМАНИЕ: приведенные выше процедуры модифицируют регистры H, L (см. главу "О- шибки системы"). После вывода нужного рисунка на экран, его можно вывести на принтер. Аналогом инструкции COPY является процедура: COPY #0EAC (3756) - Эта процедура копи- рует на принтер 22 верхние строки. Параметры всех вышеприведенных процедур подчинены тем же ограничениям, что и соот- ветствующие команды в Бейсике. 8. 5 Очистка и перемещение экрана CLS #0D68 (3435) - Основная процедура очистки экрана. Еe действие аналогично действию директивы с тем же именем. CLS_LOWER #0D6E (3438) - Очистка нижней части экрана. Она действует на всей нижней части экрана не- зависимо от ее текущих размеров и одновре- менно устанавливает ее высоту в две стро- ки. Инициализируются также системные пере- менные DF_CL и SPOSNL, определяющие поло- жение курсора. CL_LINE #0E44 (3652) - Очистка, начиная с нижнего края экра- на, заданного числа строк. Число строк со- держится в регистре B. Перед вызовом этих процедур следует убедиться, что нужные каналы открыты. Две первые из них оставляют после выхода канал "K" открытым. При стирании экрана цвета устанавливаются на основании системных пе- ременных BORDCR для нижней части экрана, а также ATTR_P и MASK_P для верхней, после чего их содержимое копируется в ATTR_T и MASK_T. CL_SC_ALL #0DFE (3582) - Перемещает содержимое всего экрана (24 полные строки) на одну строку вверх (верхняя строка исчезает). Ее можно выз- вать непосредственно из Бейсика, т.к. она не требует параметров. CL_SCROLL #0E00 (3584) - После занесения в регистр B числа строк-1 для сдвига (не менее 2) эта проце- дура продвинет на одну строку столько строк, сколько пожелаете, не трогая рас- положенных выше. Следовательно, можно иметь вверху экрана рисунок или текст, ко- торые не уничтожаются при сдвиге на одну строку. Применяя эти процедуры надо помнить, что вверх будут подниматься также обяза- тельные атрибуты в нижней части экрана. 8. 6 Считывание с клавиатуры Информацию с клавиатуры выгоднее всего снимать c системной переменной LAST_K. Проверяя состояние пятого бита переменной FLAGS, можно определить нажата или не на- жата очередная клавиша (1-нажата новая, 0-еще не нажата ни одна с момента обнуле- ния этого бита). После считывания кода клавиши, обнуляя этот бит, мы обеспечиваем себе возможность знать, модифицировалось ли еше содержимое LAST_K или нет. Дейст- вует это только при включенных замаскиро- ванных прерываниях в режиме 1. Именно в этом случае ZX-SPECTRUM автоматически вы- зывает процедуру 50 раз в секунду: KEYBOARD #02BF (703) Она просматривает всю клавиатуру, иден- тифицируя верную комбинацию клавиш, зано- сит считанный код в буфер, а также в пере- менную KASK_K и устанавливает пятый бит FLAGS. Интерпретация нажатой клавиши будет зависеть от трех системных переменных. Сначала проверяется содержимое MMODE, рассматриваемое как число со знаком в коде дополнения до двух: 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. Здесь мы поступаем также как и в случае функции с тем же самым наз- ванием в Бейсике. 8. 7 Калькулятор Различные действия над числами с пла- вающей запятой выполняются с помощью боль- шого набора процедур, занимающих прост- ранство ROM от #2F9B (12187) до #3860 (14445). Калькулятор вызывается инструк- цией RST #28, которая выполняет переход на адрес #3358 (131457). Основой работы калькулятора является 66 различных процедур, выполняющий набор ба- зовых операций на стеке калькулятора. Оче- редность их выполнения задается цепочкой байт, размещенных непосредственно за ко- мандой RST #28. Конец такой цепочки всегда помечается байтом со значением #38 (56). По необходимости мы ограничимся опера- циями, позволяющими выполнять различные численные расчеты. Допустим, что наверху стека находятся числа ... Z, X, Y. _______________________________________ | | | | | Знач. | | Состояние стека | |-------| Операция | после | |DEC|HEX| | операции | |---|---|----------|--------------------| | 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 |#1B|изменение | ...Z X-Y | | | |знака | | |31 |#1F|синус | ...Z X SIN Y | |32 |#20|косинус | ...Z X COS Y | |33 |#21|тангенс | ...Z X TG Y | |34 |#21|арксинус | ...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 NT 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 | | | |расчетов | | |88 |#3A|INT(Y+0.5)| ...Z X INT(Y+.5)| |160|#A0|дозапись 0| ...Z X Y 0 | |161|#A1|дозапись 1| ...Z X Y 1 | |162|#A2|дозапись | ...Z X Y 0.5| | | |0.5 | | |163|#A3|дозапись | ...Z X Y PI/2 | | | |PI/2 | | |164|#A4|дозапись | ...Z X Y 10 | | | |10 | | |___|___|__________|____________________| Как пример использования калькулятора рассчитаем значение 1. 5*SIN(X) +X**2*COS(X*PI/2). Допустим, что значение X находится наверху стека. Размещение за конструкцией RST #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(XPI/2) | | #04 | X X X*COS(X*PI/2) | | #20 | X X*X*COS(X*PI/2) | | #04 | X*X*COS(X*PI/2) X | | #01 | X*X*COS(X*PI/2) SIN(X) | | #1F | дозапись 1.3 (5 байтов) | | #34 | 1 | | #F1 | 2 | | #26 | 3 | | #66 | 4 | | #66 | 5 | | #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#80#00#0A. Символ #34 позволяет размещать числа в тексте программ на ассемблере. Однако, часто требуется поместить в стек параметры инструкции или значения, рассчитанные в программе. Для этого предназначено много вспомогательных процедур. Они позволяют как извлечение из стека значения, так и запись в него различных чисел: STK_TO_BC #2307 (8967) - Два следующих за собой числа (в 5-ти байтовом представлении) извлекаются из стека и заносятся на регистры B и C. Их значения должны лежать в диапазоне от -255 до 255. Иначе осуществляется возврат в Бейсик с сообщением B (будет выполнена инструкция RST #8). Эта процедура может применяться к отрицательным числам. Их знаки возвращаются в регистрах D и E. Чис- ла, извлекаемые из стека, округляются до ближайшего целого числа. STK_TO_A #2314 (8980) - Процедура, аналогичная предыдущей, с тем отличием, что из стека извлекается только одно число и после округления до целого помещается в буфер. Знак числа воз- вращается в регистр C. STACK_FETCH #2BF1 (11249) - Эта процедура извлекает из стека все число (5 байтов) и размещает его в регист- рах A, E, D, C, B. FP_TO_BC #2DA2 (11682) - Число из стека округляется до ближай- шего целого числа и размешается на ре- гистрах BC. Знак числа определяется указа- телем Z (0 - для отрицательных), если зна- чение в стеке превысило максимальное 65535, то указатель устанавливается в 1 и это единственная реакция на ошибку (воз- врата в ZX-Бейсик не происходит). STK_STORE #2AB6 (10934) - Эта процедура размещает наверху стека 5 байтов из регистров A, E, D, C, B. Число 1.3 (#81#26#66#66#66) можно занести в стек калькулятора 2-мя способами (обратим вни- мание, что 2-ой способ позволяет сэконо- мить 3 байта: LD A,#81 RST #28 LD DE,#6626 или DEFB #34 LD BC,#6666 DEFB #F1 CALL #2AB6 DEFB #26 DEFB #66 STACK_A #2D28 (11560) - Значение регистра A периодически заносится в стек в 5-байтовом представле- нии. STACK_BC #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) - Производит глобальную очистку рабо- чих областей системы Бейсик, а также стека калькулятора. Она модифицирует системные переменные, указывающие на соответствующие области памяти. Физически затираются толь- ко те байты, в которые заносятся указатели конца области. MAKE_ROOM #1655 (5717) - Вызывая ее, следует в регистрах HL дать адрес байта начала добавочного блока, а в BC размер блока. Эта процедура сама устанавливает, какие системные переменные должны быть модифицированы и в случае необходимости модифицирует их. Следова- тельно, она допускает вставку в существую- щую программу на Бейсике или в область переменных новых операторов, переменных и т. д.. RECLAIM_2 #19E8 (8168) - Обратная предыдущей функция. Здесь в HL заносится адрес первого байта, который необходимо убрать, а в BC размер стираемо- го блока. Также, как и прежде, соответ- ствующие системные переменные будут авто- матически модифицированы. CLFAR_BUFF #0EEF (8815) - Процедура очищает буфер принтера и модифицирует связанные с ним системные пе- ременные. LINE_ADDR #196E (6510) - Отыскание адреса строки с заданным номером в программе на Бейсике. Перед вы- зовом в HL заносится номер разыскиваемой строки. На выходе в HL имеем адрес этой строки или первой с большим номером. Если строка с заданным номером есть, то это сигнализируется установкой указателя Z. FREE_MEM #1F1A (7962) - Определение свободной памяти в облас- ти Бейсика, т. е. между STKEND и RAMTOP. Регистры HL и BC содержат то же самое отрицательное число, представленное в коде дополнения до 2, являющееся разницей между STKEND+80 и SP (указатель стека процессора Z80). BREAK_KEY #1F54 (8020) - Ее вызывают для проверки одновремен- ного нажатия клавиш <CS> и <BREAK>. Если они нажаты, то указатель C обнулен.