[<>] ~~TOC wide~~ {{anchor:n101}} ====== Приложение 1. MSX BASIC и машинный язык ====== \\ Покажите, как операторы языка ассемблера можно \\ чередовать с операторами данного языка. —//Д.Уолш. Руководство по созданию документации \\ для математического обеспечения// {{anchor:n1011}} ===== 1.1. Связь программы на MSX BASIC с подпрограммами в машинных кодах ===== FIXME Перед выполнением оператора [[msx:basic:|]] выполняется проверка его правильности,и в случае обнаружения ошибок на экран выводятся сообщения. Выполнимый оператор сначала преобразуется в последовательность команд,которые может "понять" процессор. Эти подпрограммы заранее помещены в ROM на этапе изготовления компьютера. Интерпретация операторов [[msx:basic:|]] занимает определенное время; в некоторых случаях, однако, это время должно быть сокращено (например, для игровых программ). Это можно сделать двумя способами: - основная программа пишется на [[msx:basic:|]], а в ситуациях, когда интерпретатор [[msx:basic:|]] не обеспечивает нужного быстродействия, используются подпрограммы, написанные в машинных кодах; - вся программа пишется на машинном языке. Отметим, что существует обширная литература по этому кругу проблем, и читатель, овладевший языком [[msx:basic:|]] и желающий научиться хорошо программировать на машинном языке, может обратиться к этой литературе [ [[bibliography#b45|[45]]], [[bibliography#b66|[66]]], [[bibliography#b67|[67]]] ]. Для обращения к подпрограммам в машинных кодах из программ на языке [[msx:basic:|]] используются оператор ''[[#DEFUSR]]'' и функция ''[[#USR]]''. {{anchor:def_usr}} Оператор ''DEFUSR'' определяет начальный адрес машинной подпрограммы. Синтаксис оператора: DEFUSR[n] = адрес , где: * DEFUSR("DEFine USing Routine"-"определить используемую подпрограмму") — служебные слова; * ''n'' — целое число,принадлежащее [0,9] и указывающее номер подпрограммы (по умолчанию значение n равно 0); * ''адрес'' — арифметическое выражение, целая часть значения которого определяет начальный адрес подпрограммы. Таким образом, в программе на языке [[msx:basic:|]] может использоваться до //десяти// подпрограмм в машинных кодах одновременно.Они различаются по номеру (от 0 до 9), который записываются вслед за служебным словом ''DEFUSR''. Например, оператор ''DEFUSR1=&HA000'' указывает, что начальным адресом программы с номером 1 является &HA000 . {{anchor:usr}} Функция USR[n](α) , где: * ''USR'' ("USer"-"пользователь") — служебное слово; * ''n'' — десятичная цифра (0,1,2,3,4,5,6,7,8,9), причем по умолчанию n=0; * ''α'' — арифметическое или строковое выражение (которое, кстати,может отсутствовать), позволяет //выполнить// подпрограмму, написанную на машинном языке. {{anchor:e10101-00}} __//Пример 1//__. \\ Пусть, например, одна машинная подпрограмма начинается с адреса &HD000, а другая - с адреса &HD100 : DEFUSR0=&HD000:DEFUSR1=&HD100 Теперь выполнить эти подпрограммы можно при помощи операторов: A=USR0(A):A=USR1(A) Значение выражения α передается подпрограмме, написанной в машинных кодах. При вызове подпрограммы число байтов,занимаемое этим значением, заносится в регистр A микропроцессора Z80 и в ячейку памяти с адресом &HF663 . ^ Содержимое регистра А ^ Тип значения выражения α ^ | 2 | Целочисленный | | 4 | Вещественный с одинарной точностью | | 8 | Вещественный с двойной точностью | | 3 | Строковый | Если аргументом функции ''USR'' является //нуль// , то в подпрограмму в машинных кодах ничего не передается! Отметим, что значение, возвращаемое функцией ''USR'', представляет собой значение ее аргумента. Если α — арифметическое выражение, то адрес его значения хранится в регистре HL . Если же α — строковое выражение, то ссылка на адрес, по которому хранится значение α, содержится в регистре DE. Ниже мы расскажем Вам и о других способах передачи аргументов машинным подпрограммам. **Примеры** \\ Некоторые люди учатся читая, другие - делая. \\ Преуспевающие делают и то, и другое. —//П.Браун// \\ Примеры, немногочисленные, лаконичные, приведенные \\ к месту, сообщают рассуждению блеск,глубину и \\ убедительность, но оно становится невразумительным, \\ если примеров и подробностей чересчур много... —//Л.К.де Вовенарг. Размышления и максимы// {{anchor:e10101-01}} __//Пример 1//__. Загрузка регистров A и BC константами. \\ {{.examples:10101-01.bas|}} \\ [[+tab|wmsxbpge>10101-01.bas]] 10 CLEAR 200,&HD000:DEFUSR=&HD000 20 DATA 3E,C3 :'LD A,C3h 30 DATA 01,A1,56 :'LD BC,56A1h 40 DATA 32,10,D0 :'LD (D010h),A 50 DATA ED,43,11,D0 :'LD (D011h),BC 60 DATA C9,"RET" :'RET 80 READ A$ 90 IF A$="RET" THEN 120 100 POKE &HD000+T,VAL("&h"+A$) 110 T=T+1:GOTO 80 120 A=USR(A) 130 PRINT "A=";HEX$(PEEK(&HD010)) 140 PRINT "BC=";HEX$(PEEK(&HD012));HEX$(PEEK(&HD011)) run A=C3 BC=56A1 Ok {{anchor:e10101-02}} __//Пример 2//__. Пересылка содержимого регистра C в регистр A. \\ {{.examples:10101-02.bas|}} \\ [[+tab|wmsxbpge>10101-02.bas]] 10 CLEAR 200,&HD000:DEFUSR=&HD000 20 DATA 0E,C3 :'LD C,C3h 30 DATA 79 :'LD A,C 40 DATA 32,10,D0 :'LD (D010h),A 50 DATA C9,"RET" :'RET 70 READ A$:IF A$="RET" THEN 110 90 POKE &HD000+T,VAL("&h"+A$) 100 T=T+1:GOTO 70 110 A=USR(A) 120 PRINT "C=A= ";HEX$(PEEK(&HD010)) run C=A= C3 Ok {{anchor:e10101-03}} __//Пример 3//__. Загрузка содержимого регистра A в память по адресу, указанному в регистре BC. \\ {{.examples:10101-03.bas|}} \\ [[+tab|wmsxbpge>10101-03.bas]] 10 CLEAR 200,&HD000:DEFUSR=&HD000 20 DATA 3E,C3 :'LD A,C3h 30 DATA 01,10,D0 :'LD BC,D010h 40 DATA 02 :'LD (BC),A 50 DATA C9,"RET" :'RET 70 READ A$:IF A$="RET" THEN 110 90 POKE &HD000+T,VAL("&h"+A$):T=T+1:GOTO 70 110 A=USR(A) 120 PRINT "A=";HEX$(PEEK(&HD010)) run A=C3 Ok {{anchor:e10101-04}} __//Пример 4//__. Проиллюстрируем работу команды EX DE, HL. \\ {{.examples:10101-04.bas|}} \\ [[+tab|wmsxbpge>10101-04.bas]] 10 CLEAR 200,&HD000:DEFUSR=&HD000 20 DATA 11,C3,98 :'LD DE,98C3h 30 DATA 21,A1,56 :'LD HL,56A1h 40 DATA ED,53,30,D0 :'LD (D030h),DE 50 DATA ED,63,32,D0 :'LD (D032h),HL 60 DATA EB :'EX DE,HL 70 DATA ED,53,34,D0 :'LD (D034h),DE 80 DATA ED,63,36,D0 :'LD (D036h),HL 90 DATA EB :'EX DE,HL 100 DATA ED,53,38,D0 :'LD (D038h),DE 110 DATA ED,63,3A,D0 :'LD (D03Ah),HL 120 DATA C9,"RET" :'RET 140 READ A$ 150 IF A$="RET" THEN 180 160 POKE &HD000+T,VAL("&h"+A$) 170 T=T+1:GOTO 140 180 A=USR(A) 190 PRINT "До команды EX DE,HL:" 200 PRINT " DE=";HEX$(PEEK(&HD031));HEX$(PEEK(&HD030)) 210 PRINT " HL=";HEX$(PEEK(&HD033));HEX$(PEEK(&HD032)) 220 PRINT "После команды EX DE,HL:" 230 PRINT " DE=";HEX$(PEEK(&HD035));HEX$(PEEK(&HD034)) 240 PRINT " HL=";HEX$(PEEK(&HD037));HEX$(PEEK(&HD036)) 250 PRINT "После еще одной команды EX DE,HL:" 260 PRINT " DE=";HEX$(PEEK(&HD039));HEX$(PEEK(&HD038)) 270 PRINT " HL=";HEX$(PEEK(&HD03B));HEX$(PEEK(&HD03A)) run До команды EX DE,HL: DE=98C3 HL=56A1 После команды EX DE,HL: DE=56A1 HL=98C3 После еще одной команды EX DE,HL: DE=98C3 HL=56A1 Ok {{anchor:e10101-05}} __//Пример 5//__. Сложение целых чисел, принадлежащих отрезку [0,255]. \\ {{.examples:10101-05.bas|}} \\ [[+tab|wmsxbpge>10101-05.bas]] 10 CLEAR 100,&HD000 'Очистка памяти 20 POKE &HD000,9 'Запись в память первого операнда 30 POKE &HD001,9 'Запись в память второго операнда 40 AD=&HD003 'Адрес начала подпрограммы 50 READ T$:IF T$="z" THEN 90 70 POKE AD,VAL("&h"+T$):AD=AD+1:GOTO 50 90 DEFUSR=&HD003:H=USR(0) 105 PRINT PEEK(&HD002) 'Печать результата 110 DATA 3A,00,D0 :'LD A,(D000) ; (D000h) ──▶ А 120 DATA 47 :'LD B,A ; (A) ──▶ B 130 DATA 3A,01,D0 :'LD A,(D001) ; (D001h) ──▶ А 140 DATA 80 :'ADD A,B ; (A)+(B) ──▶ A 150 DATA 32,02,D0 :'LD (D002),A ; (А) ──▶ (D002h) 160 DATA C9,"z" :'RET ; Возврат из подпрограммы {{anchor:e10101-06}} __//Пример 6//__. Умножение содержимого двух ячеек памяти. \\ {{.examples:10101-06.bas|}} \\ [[+tab|wmsxbpge>10101-06.bas]] 10 CLEAR 200,&HA000 20 INPUT A,B:POKE &HA100,A:POKE &HA101,B 'Ввод начальных данных 30 FOR I=0 TO 15:READ R$:POKE &HA000+I,VAL("&h"+R$):NEXT 40 DEFUSR=&HA000:Z=USR(0) 50 PRINT PEEK(&HA102) 60 DATA 3A,00,A1 :' LD A,(A100h) 70 DATA 47 :' LD B,A 80 DATA 05 :' DEC B 90 DATA 3A,01,A1 :' LD A,(A101h) 100 DATA 57 :' LD D,A 110 DATA 82 :' ADD A,D ◀────┐ 120 DATA 10,FD :' DJNZ $-1 ────┘ 130 DATA 32,02,A1 :' LD (A102h),A 150 DATA C9 :' RET {{anchor:e10101-07}} __//Пример 7//__. Нахождение суммы целых чисел от 1 до 10. \\ {{.examples:10101-07.bas|}} \\ [[+tab|wmsxbpge>10101-07.bas]] 10 CLEAR 100,&HD000:DEFUSR=&HD000:AD=&HD000 40 READ A$:IF A$="Z"THEN 80 60 POKE AD,VAL("&h"+A$):AD=AD+1:GOTO 40 80 A=USR(0):PRINT"Сумма:"PEEK(&HD00C):END 110 DATA 06,0A :' LD B,0Ah 120 DATA 3E,00 :' LD A,00h 130 DATA 80 :' ADD A,B ;◀──┐ 140 DATA 10,FD :' DJNZ D004h ;───┘ 150 DATA 32,0C,D0 :' LD (D00C),A 160 DATA C9,"Z" :' RET {{anchor:e10101-08}} __//Пример 8//__. Нахождение большего из двух чисел. \\ {{.examples:10101-08.bas|}} \\ [[+tab|wmsxbpge>10101-08.bas]] 10 PRINT"Найду большее из двух чисел (0÷255)" 20 INPUT"Первое число ";N1:INPUT"Второе число ";N2 40 POKE &HC000,N1:POKE &HC001,N2 50 CLEAR 200,&HD000:DEFUSR=&HD000:I=&HD000 70 READ A$:IF A$="z" THEN 110 90 POKE I,VAL("&h"+A$):I=I+1:GOTO 70 110 A=USR(0) 120 PRINT"Большее число:";PEEK(&HC003):END 130 DATA 3A,00,C0 :'LD A,(C000h) 140 DATA 47 :'LD B,A 150 DATA 3A,01,C0 :'LD A,(C001h) 160 DATA B8 :'CP B 170 DATA F2,0C,D0 :'JP P,D00Ch 180 DATA 78 :'LD A,B 190 DATA 32,03,C0 :'LD (C003h),A 200 DATA C9,"z" :'RET Напомним, что подрограмма в машинных кодах вызывается следующим образом: Переменная = USR[n](аргумент) , где * n — целое число, принадлежащее отрезку [0,9], которое указывает номер подпрограммы в машинных кодах. Значение //аргумента// передается подпрограмме в машинных кодах. При вызове подпрограммы число байтов, занимаемое этим значением, однозначно связанное с его типом, заносится в регистр A микропроцессора Z80 и в ячейку памяти F663h. Возможны следующие значения: * 2 — целочисленные, * 4 — вещественные с одинарной точностью, * 8 — вещественные с двойной точностью, * 3 — строковые. Адрес числовой //переменной// хранится в регистре HL; адрес строковой //переменной// находится в регистре DE. {{anchor:e10101-09}} __//Пример 9//__. Нахождение большего из двух чисел. \\ {{.examples:10101-09.bas|}} \\ [[+tab|wmsxbpge>10101-09.bas]] 10 CLEAR 200,&HF000 20 DATA ED,63,02,F3 :'LD (F302h),HL 30 DATA 3A,F8,F7 :'LD A,(F7F8h) 40 DATA 4F :'LD C,A 50 DATA 81 :'ADD A,C 60 DATA 32,00,C0 :'LD (C000h),A 70 DATA C9,"RET" :'RET 80 DEFUSR=&HF000 90 READ Z$:POKE &HF000+T,VAL("&H"+Z$) 100 T=T+1:IF Z$<>"RET" THEN 90 110 A=USR(6) 'Обратите внимание на то, что аргумент имеет целый тип! 120 PRINT" В регистре HL находится число: &h";HEX$(PEEK(&HF303));RIGHT $("00"+HEX$(PEEK(&HF302)),2) 125 PRINT" Это есть адрес аргумента функции USR" 130 PRINT" Проверяем этот факт:... и вправду ";LEFT$(HEX$(PEEK(&HF7F7)),1) 140 PRINT" Результат работы подпрограммы в машинных кодах: ";PEEK(&HC000) run В регистре HL находится число: &hF7F6 Это есть адрес аргумента функции USR Проверяем этот факт:... и вправду 6 Результат работы подпрограммы в машинных кодах: 12 Ok {{anchor:e10101-10}} __//Пример 10//__. Нахождение большего из двух чисел. \\ {{.examples:10101-10.bas|}} \\ [[+tab|wmsxbpge>10101-10.bas]] 10 CLEAR 200,&HF000 20 DATA 32,00,F3 :'LD (F300h),A 30 DATA 3A,63,F6 :'LD A,(F663h) 40 DATA 32,01,F3 :'LD (F301h),A 50 DATA ED,53,02,F3 :'LD (F302h),DE 60 DATA C9,"RET" :'RET 70 DEFUSR=&HF000 80 READ Z$:POKE &HF000+T,VAL("&h"+Z$) 90 T=T+1:IF Z$<>"RET" GOTO 80 100 A$="MSX":A$=USR(A$) 'Обратите внимание на то, что тип аргумента - строковый ! 110 PRINT"В регистре А:";PEEK(&HF300) 120 PRINT"В ячейке F663h:";PEEK(&HF301) 130 PRINT"Адрес переменной:";HEX$(PEEK(&HF303));RIGHT$("00"+HEX$(PEEK(&HF302)),2) run В регистре А: 3 В ячейке F663h: 3 Адрес переменной:8183 Ok ?hex$(peek(&h8185));hex$(peek(&h8184)) 80E7 Ok ?chr$(peek(&h80E7)) M Ok Итак, информация о типе переменной A$ хранится в ячейке с адресом &HF663, а ссылка на адрес переменной A$ (&H8183) хранится в регистре DE. \\ Если вы не тот, кто наверху, значит, \\ вы тот, кто внизу. —//С.Поттер// {{anchor:e10101-11}} __//Пример 11//__. Сортировка целочисленного массива , содержащего 5 элементов. \\ {{.examples:10101-11.bas|}} \\ [[+tab|wmsxbpge>10101-11.bas]] 10 CLEAR 200,&HA000:DEFUSR=&HA000:AD=&HA000 20 READ A$:IF A$="z" THEN 50 30 POKE AD,VAL("&h"+A$) 40 AD=AD+1:GOTO 20 50 FOR I=0 TO 4:INPUT A:POKE &HB000+I,A:NEXT 60 A=USR(0) 70 FOR I=0 TO 4:PRINT PEEK(&HC000+I):NEXT 80 END 90 DATA 0E,00 :'LD C,00 100 DATA 11,00,C0 :'LD DE,C000 110 DATA 21,00,B0 :'LD HL,B000 ◀────┐ 120 DATA 06,05 :'LD B,05 │ 130 DATA 7E :'LD A,(HL) ◀─┐ │ 140 DATA B9 :'CP C │ │ 150 DATA C2,11,A0 :'JP NZ,A011 │ ──┐ │ 160 DATA 12 :'LD (DE),A │ │ │ 170 DATA 13 :'INC DE │ │ │ 180 DATA 23 :'INC HL │ ◀──┘ │ 190 DATA 10,F6 :'DJNZ A00A ──┘ │ 200 DATA 79 :'LD A,C │ 210 DATA FE,FF :'CP FF │ 220 DATA CA,1E,A0 :'JP Z,A01E ──┐ │ 230 DATA 0C :'INC C │ │ 240 DATA C3,05,A0 :'JP A005 ──│─────┘ 250 DATA C9,"z" :'RET ◀──┘ {{anchor:e10101-12}} __//Пример 12//__. Вычисление факториала "маленьких" целых чисел \\ {{.examples:10101-12.bas|}} \\ [[+tab|wmsxbpge>10101-12.bas]] 10 CLS:KEYOFF:PRINT"Найду факториал заданного Вами целого числа" 20 INPUT"Введите целое число, меньшее 6";F 30 POKE &HC000,F 40 IF F=0 OR F=1 THEN POKE &HC003,1:GOTO 90 50 CLEAR 200,&HD000:DEFUSR=&HD000:I=&HD000 60 READ A$:IF A$="z" THEN 80 70 POKE I,VAL("&h"+A$):I=I+1:GOTO 60 80 A=USR(0) 90 PRINT "Факториал "PEEK(&HC000)"=" PEEK(&HC003):END 100 DATA 3A,00,C0 :'LD A,(C000h) 110 DATA FE,02 :'CP 2h 120 DATA 28,0A :'JR Z,$+12 ───────────┐ 130 DATA 47 :'LD B,A │ 140 DATA 05 :'DEC B │ 150 DATA 05 :'DEC B │ 160 DATA 48 :'LD C,B ◀───────┐ │ 170 DATA 57 :'LD D,A │ │ 180 DATA 82 :'ADD A,D ◀────┐ │ │ 190 DATA 10,FD :'DJNZ $-1h ────┘ │ │ 200 DATA 41 :'LD B,C │ │ 210 DATA 10,F8 :'DJNZ $-6h ───────┘ │ 220 DATA 32,03,C0 :'LD (C003h),A ◀───────┘ 230 DATA C9,"z" :'RET {{anchor:e10101-13}} __//Пример 13//__. Умножение шестнадцатиразрядных целых чисел без знака. \\ {{.examples:10101-13.bas|}} \\ [[+tab|wmsxbpge>10101-13.bas]] 20 CLEAR 200,&HF000 40 INPUT"Первое целое число";N1% 50 INPUT"Второе целое число";N2% 60 POKE &HF000,PEEK(VARPTR(N1%)):POKE &HF001,PEEK(VARPTR(N1%)+1) 70 POKE &HF002,PEEK(VARPTR(N2%)):POKE &HF003,PEEK(VARPTR(N2%)+1) 80 DEFUSR=&HF006 90 DATA ED,6B,00,F0 :' ARG1: LD HL,(F000) 100 DATA ED,5B,02,F0 :' ARG2: LD DE,(F002) 110 DATA 06,10 :' LD B,10 120 DATA 4A :' LD C,D ; Пересылка множителя 130 DATA 7B :' LD A,E 140 DATA EB :' EX DE,HL ; Пересылка множимого 150 DATA 21,00,00 :' LD HL,0000 ; Установка частичного результата в исходное состояние 160 DATA CB,39 :' MLOOP:SRL C ; Сдвиг множителя вправо 170 DATA 1F :' RRA ; Наименее значащий разряд к переносу 180 DATA 30,01 :' JR NC,NOADD-S; Если нет переноса, то пропуск суммирования 190 DATA 19 :' ADD HL,DE ; Иначе суммирование мно- жимого в частичный результат 200 DATA EB :' NOADD:EX DE,HL ; Сдвиг множимого влево 210 DATA 29 :' ADD HL,HL ; при умножении на 2 220 DATA EB :' EX DE,HL 230 DATA 10,F5 :' DJNZ MLOOP-S ; Повторение до последнего разряда 240 DATA ED,63,04,F0 :' REZ: LD (F004),HL 250 DATA C9 :' RET 260 FOR T=0 TO 31:READ Z$:POKE &HF006+T,VAL("&h"+Z$):NEXT 270 X=USR(X) 'Выполним умножение чисел N1% и N2% 280 MULT=256*PEEK(&HF005)+PEEK(&HF004) 290 IF MULT>32767 THEN MULT=MULT-65536! 300 PRINT"Ваш результат:";MULT:END ---- [<>] {{tag>MSX msxbdpl}}