Из журнала ZX-Guide#1, Рязань, 28.11.1998 Краткий урок ассемблера для ламеров  Alone Coder Предисловие. До недавнего времени я полагал, что на свете уже не сущестувует людей, которые не только не могут написать компьютерную иг- ру, но и - страшно сказать! - не могут от- личить регистр от порта. Мои наивные пред- положения развеял недавний визит на рязан- ский радиорынок в ЦПКиО. - У вас есть какие-нибудь программы для Синклера?- спросил молодой человек,подойдя к столу в центре площадки. Меня насторожило уже слово <Синклер>: может быть, кто-то еще не знает, но компь- ютер, на котором Вы читаете этот текст,на- зывается ZX Spectrum, от латинского слова, означающего, кажется, <радуга>. Я подошел ближе. - Извините, я местный кодер. Меня зовут Дима Быстров...- начал я смущенно. Разговорившись,мы обменялись адресами и простились, пообещав навещать друг друга. Через пару недель мы снова встретились; каждая моя программа явно удивила моего нового знакомого,который,вероятно,не видел свежего софта уже года два. Я скинул ему основных утилит и пожелал в скором времени написать что-нибудь хорошее. Прошло еще две недели... Он вернул мои диски и принес старый журнал в глянцевой обложке. Журнал назывался <Домашний компьютер>; это был, по правде говоря, писюшный журнал (<писюшный>- прилагательное от <писи>, ка- ковой термин мы, кодеры, используем вместо IBM PC). Рубрика <ПОМОГИТЕ!> наиболее заи- нтересовала меня. Это рубрика вопросов чи- тателей журнала и ответов на них, данных неким д-ром Хелпом. Когда я натолкнулся на ЭТИ вопросы,я не смог удержаться от смеха: <Я внимательно посмотрел на свой компьютер сзади и обнаружил там несколько интересных разъемов. Интересно, что это за разъемы и можно ли их как-то использовать? Мышь и принтер у меня уже подключены. Что можно еще подключить?> <Очень я люблю играть в игры,но играю все- гда один.Друзья мне рассказали,что во мно- гие компьютерные игры можно играть двум игрокам одновременно на разных компьюте- рах. При этом будто бы можно сражаться на разных сторонах.Как это бывает?Мне никогда не приходилось в такие игры играть>. Следующий вопрос я специально отмечаю. Теперь Вы поймете, к чему я вообще повел эту беседу. Готовьтесь... <Я недавно купил компьютер.Но пользоваться им я боюсь. Не потому,что я боюсь техники. Просто я не знаю,что надо делать,чтобы из- бежать поломок? Компьютерная техника дово- льно сложная,непонятная и совсем не похожа на другую технику. Я больше всего опасаюсь чего-нибудь поломать или испортить>. Далее следует описание того, как компь- тер следует пылесосить и протирать тряпоч- кой. Но самого главного авторы журнала не смогли понять: читатель ПОЛАГАЕТ, ЧТО АП- ПАРАТНУЮ ЧАСТЬ КОМПЬЮТЕРА МОЖНО ИСПОРТИТЬ, НАЖАВ НЕ НА ТУ КНОПКУ! Развеять подобные мифы и помочь чудако- ватым читателям компьютерных журналов по- чувствовать себя хозяевами своего компью- тера и должна эта страничка. Глава 1 о том, как побороть страх и написать простейшую программу на ассемблере Во-первых, введу несколько основных по- нятий,используемых нами и применения кото- рых Вам в будущем не избежать. КОДЕР - человек, который пишет (делает) программы в машинных кодах. МАШИННЫЕ КОДЫ - указания процессору Z80 в компьютере,сводящиеся к простейшим вычи- слительным операциям. Программы в машинных кодах ВСЕГДА быстрее написанных на каких- либо других языках(Бейсик,Паскаль,Си,...). АССЕМБЛЕР (АСМ) - метод записи М.К. на бумаге(экране,диске,..)в виде букв и цифр. Также А.- программа, которая переводит асм в М.К. ЛАМЕР - не КОДЕР. Также Л.- тот,кто лю- бит портить чужие программы. ХАКЕР - хороший КОДЕР, который изменяет чужую программу в лучшую сторону(например, вставляет вечную жизнь, красивую заставку, упаковывает игру,редко - исправляет ошибки в программе или добавляет к ней новые воз- можности). ПАМЯТЬ - память компьютера. В более уз- ком смысле - оперативная память(ОЗУ, в нее еще можно записывать). БАЙТ - символ/ число от 0 до 255/ число от -128 до 127, причем -128 соответствует 128, -127 соответствует 129, и.т.д. Из них состоит память. БИТ - одна восьмая байта.Содержит мини- мум информации:0 или 1.(Несмотря на беско- нечные повторения этой бессмысленной фразы в литературе, спешу сообщить, что минимум информации - это ноль бит,а если Вы скаже- те, что так не считается, то приведу еще пример меньшей информации:0 с вероятностью 1/3 или 1 с вероятностью 2/3.) Нумеруются в байте справа налево с нуля, причем сумма битов, помноженных на двойки в степени их номеров, даст значение байта. Лучшим Ассемблером является ALASM. Он имеет 2 режима - командный и редак- тирования. Первый устанавливается после загрузки. Вход во второй - набрать EDIT (кнопка Е) название. Например: EDIT ААА. Тут же Вы войдете в новоиспеченный тек- стовый файл ААА.H и сможете его изменить. Теперь Вы сидите перед экраном,а голова Ваша почему-то стала плохо соображать. В таких случаях я набираю первую и последнюю строчки будущей программы.Вот как они обы- чно выглядят: ORG 25000 RET Первая указывает место в памяти,где бу- дет работать программа (от 24576 до 65535) и не является командой процессору (то есть не занимает места). Последняя - выход из программы(или из подпрограммы,что в сущно- сти то же самое)туда, откуда ее осмелились запустить(вызвать). Команды типа ORG называются директивами Ассемблера. Еще пример директивы: ENT Она нужна для того,чтобы знать,с какого места запустить Вашу изобилующую ORG'ами программу,поскольку одни Ассемблеры (MASM) предпочитают последний ORG,другие (ALASM)- первый, а некоторые - вообще отказываются транслировать(ассемблировать)программу без ENT или ENT $. Строчки для вставления команд в середи- ну текста раздвигаются кнопками SS+W (<>), а сдвигаются кнопками SS+Q (<=). Сначала это может показаться неудобным,но потом Вы найдете это весьма хорошим способом. Полезна директива DEFB "text",#3F,5,"a" Она засовывает в то место памяти, где должна была бы располагаться следующая ко- манда программы,любой набор байтов (симво- лов). Аналогична команда DEFW, но она сует байты по два и не переваривает текстов.Еще загадочнее команда DEFS 40,1,2,3 - которая 40 раз подряд пихает в память байты 1,2,3. Решетка # означает шестнадцатиричное число #3F=3*16+F, где A,B,C,D,E,F соответственно равны 10,11,12,13,14,15. Но я отвлекся. Чтобы наша программа де- лала хоть что-то, хотя мы еще ничего не умеем, нагло используем какую-нибудь чужую программу из ПЗУ (там много интересного): ORG 25000 CALL 3582 RET Теперь мы вызываем программу сдвига всего экрана вверх на знакоместо(SCROLL UP CHR$) таким же образом, как нашу программу вызвала система (Бейсик,ALASM,TR-DOS,...). Чтобы запустить сей плагиат(дедовщину?) и посмотреть,как он работает,выйдем из ре- жима редактирования в режим команд. Для этого нажмите EXTEND;появится строчка ожи- дания команды редактора. Можно ввести раз- ные команды: B - начало текста, E - конец, S - поиск чего-нибудь, X - замена,и еще уж не помню что, но Вам надо нажать Q - Quit. Теперь программу стоит записать: SAVE. После этого ассемблируем: ASSEMBLE и запу- скаем: RUN. Экран сдвинулся вверх, ура. Можно также вызвать ее из Бейсика: QUIT (выход в Бейсик), RANDOMIZE USR 25000. Возврат в ALASM - RANDOMIZE USR 23600. Из встроенного отладчика StS: DEBUG (отладка),JUMP 25000. RANDOMIZE USR 23600. Переход в редактирование - EDIT. Для полного овладения системой Вам сто- ит прочитать HELP'ы к ALASM и StS.Дальней- ший текст для компактности предполагает, что Вы уже умеете с ними обращаться. Глава 2 обо всём понемножку Регистр является маленькой переменной, которая хранится не в памяти,а в процессо- ре. В регистре 1 байт. Названия регистров: A B C D E H L Я перечислил только регистры общего на- значения,всего же регистров 24.6 последних собраны в пары, которые называются просто <регистровыми парами>(reg pair,rp) и могут использоваться как двухбайтное число/адрес (из 160К памяти одновременно адресуются 64 К,посему адрес байта передается 2 байтами) Регистру можно присвоить значение командой LD E,100 или LD BC,40000 или LD A,H но не LD HL,BC Метка (label)аналогична номеру строки в Бейсике за тем ислючением, что ее не нужно помещать в каждой строке.Метку можно испо- льзовать в выражениях вместо соответствую- щего числа.Метка содержит 7 символов и на- чинается с буквы. Пример: ORG 25000 LD DE,TEXT LD BC,TEND-TEXT CALL #203C ;опять программа из ПЗУ RET TEXT DEFB "TEXT - это метка",13 TEND Печатается текст по адресу TEXT=25010 длиной TEND-TEXT=17 байт. Все эти вычисле- ния производит ALASM при трансляции. Если Вы не желаете, чтобы программа вы- полнялась только подряд,можно использовать переход к метке: ORG 25000 LD BC,#50F0;B=#50=80,C=#F0=240 JP 8933 На экране появляется точка. JP 8933 пе- редает управление программе в ПЗУ,которая, естественно,уже содержит RET,так что можно не волноваться по поводу возврата. Переход не более чем на 127 байт вперед/назад на- зывается коротким или относительным и за- писывается JR адрес. Когда нужно выполнить что-то несколько раз подряд(совершить цикл),в частности,ис- пользуется команда DJNZ адрес: ORG 25000 LD B,80 CYCLE LD (BUF),BC;запишем;8933 портит B LD C,B CALL 8933 LD BC,(BUF);в BC старое значение DJNZ CYCLE RET BUF DEFW 0 Нарисуется диагональ.DJNZ вычитает из B единицу и,если не получился ноль,переходит по указанному адресу. Легко подсчитать,что поэтому цикл выполнится ровно столько раз, сколько было в B при входе в него(т.е.80). Круглые скобки означают,что имеется в виду не число, а ячейка памяти с этим номером. LD (BUF),BC записывает BC по адресу BUF. Использовать ячейки с метками для хра- нения регистров неудобно,поэтому придуманы команды: PUSH rp(пихнуть) и POP rp(вытолк- нуть).Первая запоминает rp в области памя- ти, именуемой стеком, вторая берет оттуда текущее значение в rp. Много значений, за- сунутых в стек,снимаются оттуда в обратном порядке. Там же хранится адрес,по которому переходит команда RET. Поэтому число PUSH и POP должно совпадать. Пример: ORG 25000 LD B,80 CYCLE PUSH BC LD A,80 SUB B LD C,A CALL 8933 POP BC DJNZ CYCLE RET Нарисуется обратная диагональ. Команда SUB r относится к категории арифметическо- логических и вычитает из A регистр r. Дру- гие а-л команды: ADD A,r;прибавить r к A ADD HL,rp;прибавить rp к HL ADC A,r;A=A+r+CY ADC HL,rp;HL=HL+rp+CY SBC A,r;A=A-r-CY SBC HL,rp;HL=HL-rp-CY DEC r/rp;уменьшить rp на 1 INC r/rp;увеличить rp на 1 AND r;побитовое A И r записывается в A.Об- нуляются все биты A, которым соответствуют нулевые биты r. OR r;побитовое A ИЛИ r записывается в A. В единицу обращаются все биты A,соответству- ющие которым биты r были равны 1. XOR r;то же, но побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ. B каждый бит A записывается 1, если биты с этим номером в A и r были разные;иначе 0. CP r;сравнение A с r CPL;A=255-A.Все биты инвертируются(0 1) NEG;А=-А. DAA;Двоично-десятичная коррекция; произво- дите ее сразу после сложения/вычитания,ес- ли с/в'емые числа были двоично-десятичные. Вряд ли она Вам пригодится,т.к.д-д числа - это метод записи двух цифр в байте, причем д-д #34 считается равным 34. Несколько слов о том,что же такое CY.Он называется флагом(признаком,битом)переноса и устанавливается в 1, если результат сло- жения превысил 255(65535 в случае 2-байто- вого сложения) или при вычитании большего числа из меньшего или в других особо ого- воренных случаях. Еще используется флаг Z, который равен 1, если результат а-л опера- ции равен нулю,и нулю в других случаях. За это он называется флагом нуля. Все флаги находятся в регистре флагов F,который вме- сте с регистром A составляет еще одну(чет- вертую)r.p.AF,которая,к сожалению,не может использоваться в двухбайтовых вычислениях. Сравнение - то же вычитание, но резуль- тат его не запоминается,а только соответс- твующим образом меняются флаги: Z=1,если A=r;Z=0,если A<>r; CY=1,если A=r. Зачем нужны флаги? Да для того,чтобы по ним потом получать условия ветвления прог- раммы, вызова последней или выхода из нее. Команда условного короткого перехода выг- лядит так: JR cc,<метка>,где cc - условие: Z - Z=1 Поскольку есть еще флаги,то есть NZ - Z=0 и дополнительные редко использу- C - CY=1 емые условия,употребляемые в ко- NC - CY=0 мандах длинного перехода JP. То есть,если условие выполнится,то сле- дует осуществить переход,иначе - нет. Глава 3 несколько важных практических советов Программа становится Вашей лишь тогда, когда Вы трудились над ее созданием. Кста- ти,не стоит также радоваться после написа- ния любой программы, если последняя до Вас была написана хотя бы одним человеком. Существует два способа написания прог- рамм на асме: на бумаге ручкой/карандашом с последующим набором на компьютере и не- посредственно сидя за/перед экраном.Первый способ хорош тем,что можно писать програм- мы вдали от компьютера и плох низкой прои- зводительностью и повышением числа ошибок; второй обходит недостатки первого и теряет его достоинства.Желательно владеть обеими. Несмотря на то,что Пентагон 128-й, пом- ните, что не каждый компьютер - Пентагон. Следовательно, не стоит пользоваться двумя экранами, когда довольно одного, а также жрать все 71680 тактов процессора или ста- вить первой командой прерывания EI. НИКОГДА не управляйте включением/выклю- чением дисковода для проверки наличия дис- ка или проигрывания через дисковод музыки! В отечественном дисководе 5313 при этом нарушается работа механизма опускания го- ловки на диск и дисковод НЕ МОЖЕТ ПИСАТЬ ИНФОРМАЦИЮ! Имейте копии своих важных программ на своих и/или чужих дисках. Последнее иногда даже надежнее. Не имейте копий барахла. Сохраняйте системную дорожку. Экономьте свое время, e.g. не смотрите дважды одни и те же телепередачи,не ходите на дискотеки и пьянки. Помните: жизнь у человека одна и короткая, а вспомнят о Вас лишь если Вы успеете сделать за нее много. Научитесь говорить, печатать, ходить и ду- мать быстро. Имейте терпение; ошибки в программах случаются и у мэтров из CodeBusters,но они умеют их исправлять=>и Вы научитесь.Сперва структуру сложной программы Вы не сумеете удержать в памяти,но через небольшое время регулярной работы на компьютере размер программы, набираемой зараз,станет неогра- ниченным. Не составляйте программу из кусочков! Метки в программах рекомендуется приду- мывать так: название подпрограммы+буква/ цифра, например: MOVTXT0 в п/п MOVTEXT; READKYQ в READKEY; можно SYMN13 в SYM. Если кто-то скажет Вам, что некий метод является всегда лучшим, не верьте ему. Для примера возьмем перемещение памяти.Медлен- ный LDIR незаменим в текстовом редакторе; LDI весьма полезен при вертикальном сдвиге экрана и недостатке памяти под сдвигалку; POP HL:LD (...),HL используется в сдвигах, мультиколорах и мэппингах;POP rp...PUSH rp хорошо для ускорения мэппинга (см.статью в следующем номере); LD rp,...PUSH rp доселе использовалось только для рисования сето- чек, хотя одним махом увеличивает ширину мультиколора до 24 CHR$ +позволяет вывести экран в одно прерывание (см.статью). Несмотря на то, что переключение двух изображений дает разнообразные графические режимы,помните,что это приводит к мерцанию экрана, которое,возможно,годится для демо, но не для программы, которую пользователь активно использует. Можно даже вспомнить: Quo vicet Iovi,non licet bovi,что означает <Что может Юпитер, то не позволено быку>. Переключение трех экранов не используйте никогда, поскольку это влечет за собой не мерцание, но моргание экрана. Не ограничивайте сложность составляемых Вами программ, оправдываясь тем, что некто является очень умным или у него больше свободного времени,чем у Вас. Не боги гор- шки обжигают! У меня вообще нет свободного времени, однако написал же я этот журнал! Продолжение следует... Из журнала ZX-Guide#2, Рязань, Ноябрь 1999  Alone Coder Урок ассемблера для ламеров. (продолжение) Сегодня я собираюсь рассказать вам о методах опроса клавиатуры, прерываниях, а также постараюсь исправить свои ошибки,ко- торые допустил в предыдущем номере. 1. Начну с ошибок. В прошлый раз я так страшно загрузил вас описанием директив ассемблера, причём весьма неполным и ошибочным описанием. Но если вы внимательно читали текст об ALASM, то, скорее всего,пропустили этот бред мимо ушей.Дальнейшее относится к тем другим,кто поверил этому бреду и не читал описания.Во избежание. Нельзя сказать,что директива не занима- ет места в памяти.Взять хотя бы DEFB (DB): она, конечно же, не является командой про- цессору,но память жрёт честно:ровно столь- ко,сколько байт информации после неё запи- сано.Или другая директива: DUP 8 RR C RLA EDUP После компиляции этот кусок будет выг- лядеть так: RR C RLA RR C RLA RR C RLA RR C RLA RR C RLA RR C RLA RR C RLA RR C RLA Мало того, что он занимает больше памя- ти,чем просто RR C RLA Ведь при этом вся эта память заполняет- ся сплошь командами процессора! Насчёт директивы ENT я написал полную ерунду.Скажу честно:это потому,что я тогда ещё не разу её не использовал.На самом де- ле эта директива ходит в паре с DISP и ог- раничивает снизу кусок,которому DISP свер- ху приписала работать в других адресах (а не по тому адресу, по которому этот кусок компилируется). Пример: ORG #6000 LD HL,KUSOK LD DE,#4000 PUSH DE LD BC,KUSEND-KUSOK LDIR RET KUSOK DISP #4000 <какая-то программа, сплошь напичканная CALL,JP и т.п.,и потому неперемещаемая> ENT KUSEND Кстати, если метки KUSOK и KUSEND вста- вить внутрь блока DISP-ENT, то эти метки, так же как и все остальные в этом блоке, будут пересчитаны, и,естественно,будут со- держать совсем не те значения,какие бы нам хотелось. В директиве ORG допустимо два парамет- ра, причём если их два,то второй - это но- мер странички,куда мы компилируем. А прог- рамма запускается, между прочим,не по пер- вому ORG, а по последнему! Директива INCLUDE <имя>[,<страничка>] служит для подгрузки модулей для длинных программ. При компиляции ассемблер,натыка- ясь на эту директиву,подгружает соответст- вующий файл (если его ещё нет в памяти), компилирует его, и только затем продолжает компиляцию основной программы с того мес- та, где она была прервана. Директива INCBIN <имя>[,<страничка>] подгружает кодовый файл (кстати,можно ука- зыать расширение через точку) по текущему адресу компиляции, и перемещает этот адрес компиляции на конец этого файла. 2. Опрос клавиатуры. Клавиатура старого ZX Spectrum 48k со- держала 40 клавиш.Остальные клавиши расши- ренной клавиатуры являются их сочетаниями, например BREAK=CAPS/SPACE,EXTEND=CAPS/SYM- BOL,TRUE VIDEO=CAPS/3 и т.д. 40 клавиш собраны в 8 полурядов по 5 клавиш в каждом: #F7FE: 1 2 3 4 5 #EFFE: 6 7 8 9 0 #FBFE: Q W E R T #DFFE: Y U I O P #FDFE: A S D F G #BFFE: H J K L Enter #FEFE:CapsZ X C V #7FFE: B N MSymSpace Через некоторое время я объясню,что это за числа слева, но сперва расскажу о том, как процессор работает с периферийными ус- тройствами (т.е. вообще со всеми устройст- вами, кроме памяти). ____ Устройства выбираются при IORQ=0 (т.е. когда этот сигнал активен).Далее устройст- ва, в_которые записывают,должны проверить, что WR=0 (т.е.активен сигнал записи),а ус- тройства, из_которых принимают данные,про- веряют,что RD=0 (активен сигнал чтения), а потом, все устройства проверяют определён- ную комбинацию адресных сигналов,которая у каждого устройства своя. Например, пищалка (BEEPER) выбирается при A0=0. Откуда берутся эти сигналы?Естественно, с процессора. Комбинация ____ __ IORQ=0, WR=0 вырабатывается при командах OUT,а комбина- ция____ __ IORQ=0, RD=0 - при командах IN. Вроде бы,мы об этих командах ещё не го- ворили. Итак: OUT (<байт>),A - записать аккумулятор в порт, адресом которого служит: A (старший байт)*256+<байт> (младший байт).Содержимое аккумулятора выводится на шину данных. Ад- рес порта, естественно, выводится на шину адреса. OUT (C),<регистр> - записать регистр в порт,адрес которого содержится в регистро- вой паре BC. Регистром может быть:A,B,C,D, E,H,L. OUT (C),0 - записать ноль в порт BC.Ко- манда недокументирована, многими ассембле- рами не поддерживается. На процессоре Z180 приводит к сбросу. IN A,(<байт>) - считать из порта данные в аккумулятор. Адрес порта =A*256+<байт>. IN <регистр>,(C) - считать в регистр данные из порта BC. IN F,(C) - считать данные из порта BC без запоминания. Вместо этого соответству- ющим образом устанавливаются биты флагово- го регистра.(Каким таким образом они уста- навливаются,не знаю.У Ларченко и Родионова не уточняется.) Команда недокументирована. В некоторых ассемблерах она записывается как IN (HL),(C). Команды IN r,(C) и OUT (C),r называются обращением к порту с полной адресацией, в отличие от неполной адресации OUT (b),A.Ко многим портам (в том числе, к музыкальному сопроцессору AY) можно обращаться только с полной адресацией. Существуют также команды блокового об- ращения к портам.Мне как-то не приходилось их использовать. Те загадочные числа, которые я привёл против названий клавиш в полурядах,являют- ся как раз адресами соответствующих пор- тов.А считать из этих портов можно следую- щую информацию: бит 7 - не определён; бит 6 - чтение с магнитофона. На многих компьютерах не реализовано. На некоторых оно реализовано так,что оттуда считываются случайные значения (из-за того,что я забыл замаскировать этот бит по AND,в первом но- мере журнала жутко колбасила стрелка); бит 5 - не определён; биты 4-0 - состояния клавиш полуряда. Если клавиша нажата,считывается 0,а в про- тивном случае - 1.Более младшие биты отно- сятся к более крайним клавишам. Пример использования: START CALL <подпрограмма> LD A,239 ;старший байт полуряда 6-0 IN A,(254) ;считываем данные RRA ;0-й бит переходит в CY JR C,START ;крутим,пока не нажмут 0 На самом деле каждый порт клавиатуры выбирается по двум сброшенным битам:биту 0 и ещё какому-то в пределах от 8 до 15.Если сбросить несколько старших битов,выберутся сразу несколько полурядов, и их содержимые наложатся по AND. Пример: LOOP XOR A ;все биты сброшены IN A,(254) ;опрашиваем все полуряды CPL ;почти эквивалентно комбинации: AND 31 ; AND 31:CP 31, но короче JR Z,LOOP ;пока не нажмут ANY KEY Другим методом опроса клавиш является использование подпрограмм ПЗУ и результа- тов работы прерывания IM 1.Например,проце- дура 8020 сбрасывает CY, если нажат BREAK. А работать с IM 1 можно таким образом: RES 5,(IY+1) ;сброс флага "ANY KEY" NOKEY HALT ;если убрать HALT,может подви- BIT 5,(IY+1) ;снуть на нек. компах! JR Z,NOKEY ;пока не нажмут что-либо LD A,(23560) ;сист.переменная LAST_K В результате в аккумуляторе будет код последней нажатой клавиши. Будьте осторож- ны: если установлен режим CapsLock, то вы будете считывать оттуда сплошь большие бу- квы! А установить CapsLock можно так: LD (IY+48),8 ;сист.переменная FLAGS2 В своём AC Edit я успешно комбинирую оба способа опроса клавиш. 3. Некоторые другие порты. Тот же порт (254), выбранный на запись, отвечает за изменение цвета бордюра,пищал- ку и вывод на магнитофон. Биты у него та- кие: биты 7-5 - не используются; бит 4 - состояние пищалки.Чтобы вызвать звук,необходимо постоянно переключать этот бит; бит 3 - вывод на магнитофон; биты 2-0 - цвет бордюра.У бордюра может быть 8 различных цветов. Яркость в этих цветах отключена.Чтобы получать на бордюре статичные картинки,нужно каждый раз от на- чала кадрового прерывания изменять опреде- лённым образом этот цвет, пока электронный луч прорисовывает экран. Пример: XOR A ;чёрный цвет OUT (254),A ;бордюр станет чёрным Порт 31 (#1F) на чтение повествует о состоянии кемпстон-джойстика: биты 7-5 - всегда равны 0,если джойстик подключен, а в противном случае об их сос- тоянии нельзя сказать ничего определённо- го; бит 4 - огонь (1 - нажат,0 - отпущен); бит 3 - вниз (аналогично); бит 2 - вверх; бит 1 - вправо; бит 0 - влево. Порт 32765 (#7FFD) - порт расширенной памяти (на запись). На чтение невозможен (Кроме нескольких патологов). Назначение битов: биты 7-6 - на 128k не используются; бит 5 - защёлка порта #7FFD. Если этот бит установить, компьютер становится сорок восьмым со всеми вытекающими из этого пос- ледствиями.После сброса всё приходит в но- рму; бит 4 - если 0,то в адресах 0-16383 бу- дет торчать 128-е ПЗУ.Обычно это не нужно, и этот бит устанавливают в 1, тогда в ниж- них адресах будет 48-е ПЗУ; бит 3 - номер экрана: если 0 - то обыч- ный "первый" экран 16384-23295 из странич- ки № 5,иначе - "второй" экран из странички номер 7; биты 2-0 - номер странички памяти,кото- рая будет помещена в область 49152-65535. Здесь нужно остановиться подробнее. Всего у 128k, как можно догадаться, 8 страничек ОЗУ по 16k. От 16384 до 32767 всегда находится страничка № 5,от 32768 до 49151 - страничка № 2,а вот выше - уже лю- бая, какая вам нравится (включая 2 или 5). По выходе из ассемблера устанавливается страничка № 0, сам ассемблер в № 4,монитор в № 7, а текст вашей программы,скорее все- го, в № 6. Страничку № 3 ассемблер исполь- зует под таблицу меток. Итак: Если вы не собираетесь использовать мо- нитор,смело затирайте странички № 3 и № 7. (Только не ассемблируйте на № 3 и на № 7 с #C000 по #C8FF, т. к. она используется как буфер при подгрузке с диска.) Странички №№ 0, 1 безусловно свободны. А если вам стало уж совсем тесно, можете затирать и ассемб- лер вместе с исходником (соответственно №№ 4, 6). , Есть, конечно, Spectrum'ы и с большим объёмом памяти (до 2 Mb),но распространены они мало. Так что писать под них не стоит, тем более что всё равно вы не сможете спо- лна использовать все возможности расширен- ной памяти, не имея таковой. В связи с большим количеством разнооб- разных патологов с разными вариантами де- шифрации порта #7FFD, возникла так называ- емая проблема порта #FD.Название её связа- но с тем,что на компьютерах "Пентагон-128" дешифрация порта расширенной памяти осуще- ствляется всего по 2 битам: A1 и A15,в ре- зультате чего к этому порту стало возможно обращаться по неполной адресации: LD A,23 ;7-я страничка OUT (#FD),A ;записываем её № в порт Вы не находите, что это несколько удоб- нее, чем LD A,23 LD BC,32765 OUT (C),A Вот так многие и подумали, и во многих программах (например,by CodeBusters) стоит именно неполная адресация порта. Разумеет- ся,на некоторых компьютерах такие програм- мы не работают. Например, вышеизложенный пример на "Скорпионе" воспримется как об- ращение к порту #1FFD, а это уже, понятно, совсем другой порт (переключение TURBO, включение в область 0-16383 страницы тене- вого ОЗУ № 8, переключение теневых страниц ОЗУ, ПЗУ и ещё неизвестно чего). Я считаю (и многие другие тоже так счи- тают),что все Спектрумы равноправны,и если программа не работает кое-где из-за такой мелочи, как неполная дешифрация портов, то это плохая программа. Разумеется,некоторые вещи, например, цифровую музыку с высоким качеством, можно реализовать только на не- которых типах Спектрумов ("Пентагон", KAY, ATM и т.п.),но это совсем другая проблема. Это пусть вам дядя Зонов объясняет,зачем в его компьютерах процессор тормозится при общем поле памяти.А если говорить по обще- му счёту,то если поддерживать только "Пен- тагон"(говорю это несмотря на то,что у ме- ня самого "Пентагон"),то синклеристы,поль- зующиеся другими типами Спектрумов (а их не меньше 40%) могут серьёзно обидеться и уйти со Спектрума. Хорошо нам от этого бу- дет? Вот так-то. Между прочим, даже демки теперь работают на "Скорпионах". Учитесь! Порты 65533 (#FFFD),49149 (#BFFD) отве- чают за доступ к музыкальному сопроцессору AY.У сопроцессора 16 регистров разного на- значения. Для того,чтобы записать что-то в какой-либо из них, нужно заслать его номер в порт 65533, а потом данные,предназначен- ные для этого регистра, спустить в порт 49149.Если надо,наоборот,считать данные,то после записи № регистра в порт 65533 нужно считать данные из того же порта 65533. Все анализаторы громкости во всяких там boot' ах основаны именно на чтении из AY. Назна- чение отдельных регистров: №№ 0, 1 - содержит 12-разрядный период (не частоту,а именно период!) ноты в кана- ле A. Причём регистр 1 - старший; №№ 2, 3 - период канала B; №№ 4, 5 - период канала C; № 6 - пятиразрядный период шума; № 7 - управление звуком: биты 6,7 - указывают направление об- мена по параллельным портам, и посему нас не интересуют; биты 3-5 - если 1, то запрещён вывод шума соответственно в канал A,B,C; биты 0-2 - если 1, то запрещён вывод ноты в канал A,B,C. Зато взамен,если мани- пулировать громкостью в соответствующем канале, можно получить цифровую музыку! А если включена огибающая, то она будет зву- чать сама по себе! № 8 - 4-битная громкость канала A.Заме- тьте, что шкала громкости не линейная, а, скорее,логарифмическая.Если установить 4-й бит этого регистра,то громкости уже не бу- дет, а взамен будет накладываться огибаю- щая; №№ 9,10 - аналогично для каналов B,C; №№ 11,12 - период огибающей. При проиг- рывании звука на огибающей используется только младший байт (№ 11); № 13 - 4-битная форма огибающей: 0 - плавное затухание до нуля; 12 - пилообразный звук; 13 - плавное нарастание до максимума; 14 - зигзагообразный звук на октаву ниже, чем 12; №№ 14,15 - используются для обмена по параллельным портам. Конечно, вряд ли вы будете сами играть музыку через прямое обращение к AY,так что в качестве примера покажу только, как вык- лючить звук: LD DE,#E00 LD C,#FD OFF0 LD B,#FF DEC D OUT (C),D ;выбираем № регистра LD B,#BF OUT (C),E ;записываем в него ноль JR NZ,OFF0 ;и так до 0-го регистра 4. Прерывания. Каждые 20 миллисекунд (1/50 секунды) на процессор приходят кадровые прерывания.Это выражается в том,что на ножку INT подаётся отрицательный импульс. Длина импульса, как водится, у каждого патолога своя,но не бу- дем об этом. Если во время прихода импульса были включены прерывания (командой EI,выключаю- тся же они командой DI), то, в зависимости от текущего режима прерываний, процессор начнёт выполнять: а) (режим IM 0) команду, которая пришла с шины данных (не используется); б) (режим IM 1) подпрограмму RST 56; в) (режим IM 2) подпрограмму, адрес ко- торой он прочитает из ячейки с адресом: I*256+<шина данных>,где I - регистр векто- ра прерываний. В любом случае перед входом в подпрог- рамму обработки прерываний процессор отк- лючает прерывания. В настоящий момент нас более всего ин- тересует режим IM 2, т.к.в этом режиме мо- жно вставить собственный обработчик. Поскольку состояние шины данных не оп- ределено в общем случае (т.е. не на всех компьютерах оттуда считывается стабильно 255),то желательно,чтобы адрес обработчика не зависел от состояния шины данных :).Это достигается однородным заполнением одина- ковыми числами так называемой таблицы пре- рывания, длиной 257 байт (для каждого из 256 возможных состояний ШД из таблицы счи- тываются 2 одинаковых байта).Причём табли- цу эту следует размещать в так называемой быстрой памяти (странички № 0 или № 2), а то некоторые патологи могут подвиснуть... Пример: MUZ EQU #C000 ORG MUZ + INCBIN "MUSIC" ORG #6000 DI CALL MUZ ;инициализируем музыку LD HL,#FE00 LD A,H LD I,A ;I=#FE LD DE,#FE01 LD BC,#100 LD (HL),#5C LDIR ;заполняем таблицу прерывания LD L,(HL) LD H,L ;HL=#5C5C LD (HL),195 ;JP INC HL LD (HL),IMER INC HL LD (HL),'IMER IM 2 EI <далее программа будет работать под музыку> IM 1 ;выключаем IM 2 JP MUZ ;выключаем музыку и выходим IMER PUSH AF PUSH BC PUSH DE PUSH HL PUSH IX PUSH IY CALL MUZ+5 ;вызываем музыку POP IY POP IX POP HL POP DE POP BC POP AF EI RET То есть,если повесить на прерывание ка- кое-либо действие (например, проигрывание музыки), то программа уже не должна будет за этим следить, и мы получаем некоторое подобие многозадачности (много-,а не дву-, потому что никто не мешает нам повесить на прерывание несколько разных задач, напри- мер, музыку и мультиколор). Со включенным прерыванием нельзя вхо- дить в TR-DOS, иначе при первом же вызове прерывания TR-DOS исчезнет, и на её месте окажется BASIC.И при возвращении из преры- вания мы попадём немного не туда :). Спо- соб, как обойти это,указан в книге "TR-DOS для пользователей и программистов". На са- мом деле всё это не нужно.Просто отключай- те прерывания при входе в TR-DOS. Если не хватает места под таблицу пре- рывания, поместите её в медленную память, например,по адресу #5B00.Но учтите,что то- гда ваша программа будет работать не вез- де.Если и в медленной памяти нет места,мо- жете оставить от таблицы всего 2 байта по адресу I*256+255 (так сделано в TLW2).Этим вы ещё сильнее уменьшаете число своих по- льзователей.Самый последний вариант: I=59. Тогда таблица (длиной 257 байт для 48k,или 2 байта для 128k) будет в ПЗУ, а процедура обработки прерываний - по адресу 65535.По- ставьте туда JR, а по адресу 65524 (куда указывает JR) поставьте обработчик. Теперь работоспособность вашей программы, помимо модели компьютера,ограничена ещё и версией ПЗУ... 5. Полезные советы. Лучше всего начинать набирать программу со строк: ORG #6000 LD (QUIT+1),SP QUIT LD SP,0 LD IY,23610 IM 1 LD HL,10072 EXX EI RET END Кто знает,какие регистры может испорти- ть ваша программа, пока она ещё глючит? А метка END позволяет вам всегда узнать дли- ну программы.Для этого после ассемблирова- ния в командном режиме введите: COUNT END-#6000 Советую также поставить в программу по- дпрограмму печати 16-разрядных чисел и вы- зывать её время от времени в главном цикле работы программы. Это позволит вам контро- лировать зависание. Подпрограмму см.в раз- деле "Этюды". Пишите программу красиво и удобно для исправления.Чаще используйте CALL.Выделяй- те законченный функциональный блок в под- программу, даже если он вызывается только один раз.Сохраняйте регистры в подпрограм- мах.Не думайте о перемещаемости,используй- те JP вместо JR. Все адреса таблиц,музыки,спрайтов и др. заносите в EQU.Это позволит вам легко сме- нить эти адреса, если что. Последние опыты показали, что даже номера используемых страниц памяти могут измениться, и их тоже надо заносить в EQU. Когда ваша программа превысит 4k (а у меня уже 3 таких программы), её текст нач- нёт не помещаться в 16k-буфере. Придётся выделить модуль и подгружать его по дирек- тиве: INCLUDE "MYUNIT",1 Подгружаться он будет в другую странич- ку, и это позволит вам одновременно редак- тировать оба текста.Для модуля следует от- бирать подпрограммы, которые вам не прихо- дилось исправлять долгое время (я называю их "бессмертными"). Подгружайте INCBIN'ом данные в странич- ки разве что только во время отладки.Лучше всего подгружать их непосредственно на хвост программы,а потом перекидывать LDIR- 'ом. Тогда программу будет легче отгрузить в откомпилированном виде и запаковать.Сто- ит ли говорить о том,что прогу,раскиданную по страничкам,запаковать вообще ни в одном упаковщике нельзя? Продолжение следует... Из журнала ZX-Guide#3, Рязань, 03.12.2000  N!KPhE/Invaders9 (30982b) Урок ассемблера для ламеров. (продолжение) Ко всем на удивление, этот третий номер будет вести N!KPhE.Постараюсь всё излагать понятно и правильно.Посмотрим,какой из ме- ня педагог ;). Начнём с самого главного,без знания че- го писать программы просто невозможно. Глава 1 "ЭКРАH" 1 "Экранная область" Мне теперь понятно почему Alone Coder бросил написание этой статьи: он знал, что объяснить простому ламеру строение экран- ной области - невозможно! Но деваться не- куда... =8-[ ] Размер экрана в нашем Speccy составляет ни много,ни мало,а 256 точек/пиксел/бит по горизонтали и 192 по вертикали. Каждые 8x8 пикселов - это одно знакоместо.Таким обра- зом, экран делится на 24 символьные строки по 32 знакоместа в каждой. Экранная область в целом делится на две части: основной экран.......[#4000-#57FF] (6144b) атрибуты.............[#5800-#5AFF] (768b). Каждый байт состоит из восьми бит/пик- селов (нумерация идет справа налево): +-+-+-+-+-+-+-+-+ |7|6|5|4|3|2|1|0| <-структура байта +-+-+-+-+-+-+-+-+ Сумма битов, помноженных на двойку в степени их номеров,даст значение байта. Основной экран делится на три абсолютно равных сегмента (по 2048 байт каждый): первый сегмент...............[#4000-#47FF] второй сегмент...............[#4800-#4FFF] третий сегмент...............[#5000-#57FF] Для того, чтоб лучше понять,приведу мале- нький кусок верхнего края первого сегмента +-----+-----+-----+-----+-----+-----+----- |#4000|#4001|#4002|#4003|#4004|#4005|#4006 |#4100|#4101|#4102|#4103|#4104|#4105|#4106 |#4200|#4201|#4202|#4203|#4204|#4205|#4206 |#4300|#4301|#4302|#4303|#4304|#4305|#4306 |#4400|#4401|#4402|#4403|#4404|#4405|#4406 |#4500|#4501|#4502|#4503|#4504|#4505|#4506 |#4600|#4601|#4602|#4603|#4604|#4605| ... |#4700|#4701|#4702|#4703|#4704| ... | ... +-----+-----+-----+-----+-----+-----+--- |#4020|#4021|#4022|#4023| ... | ... | | |#4120|#4121|#4122| ... | ... | ... | |#4220|#4221| ... | ... | ... | ... | |#4320| ... | ... | ... | ... |#45FF| | ... | ... | ... | ... |#46FE|#46FF| | ... | ... | ... |#47FD|#47FE|#47FF| ------+-----+-----+-----+-----+ Один адрес - это байт.Одна клетка - это знакоместо.Упорядочные клетки по вертикали - это столбец,по горизонтали - строка. Так как я сильно сомневаюсь,что вы что- то здесь поняли,я приведу простую прогзу в БЕЙСИКЕ, которая последовательно заполняет экранную область байтом 255(все восемь бит включены,т.е. равны единице). С её помощью вы сможете разобраться поточнее (хотя,что- бы как следует понять структуру экрана,на- до с ним работать) FOR A=16384 TO 22527:POKE A,255:NEXT A С областью атрибутов проще.Каждый атри- бутный байт (цвет) привязан к соответству- ющему знакоместу. Начинается область атри- бутов с адреса #5800 и на экране проходит "линейно",т.е.она идентична декартовой си- стеме (которую проходят в школе) с тем от- личием, что начало координат расположено в верхнем левом углу. Можно вывести простую формулу расчёта адреса атрибута по заданным (X,Y) коорди- натам: ATTR=Y*32+X+#5800. Структура атрибутного байта: +-+-+-+-+-+-+-+-+ |7|6|5|4|3|2|1|0| +-+-+-+-+-+-+-+-+ биты 0..2 - цвет тона (INK) биты 3..5 - цвет фона (PAPER) бит 6 - яркость для тона и фона (BRIGHT) бит 7 - мерцание (FLASH) Напоследок в эту главу приведу процеду- рку,которая выставляет цвет по заданным X, Y координатам в знакоместах: LD HL,Y*256+X ;H=Y,L=X LD A,H AND 7 RRCA RRCA RRCA ADD A,L LD L,A LD A,H RRA RRA RRA AND 3 OR #58 LD H,A LD (HL),COLOR Совсем забыл:в экранной области есть ещё одна, третья, область, которая значительно отличается от других.Называется она бордюр (BORDER).Она не имеет ни каких привязанных адресов, но может принять один из восьми цветов: LD A,COLOR ;цвет (0-7) OUT (#FE),A Подробней про него можно узнать во вто- ром уроке для ламеров (ZX-Guide'2). Глава 2 "Флаги" 2 "Влияние на флаги" Сначала я не хотел очередной раз упоми- нать о них,но узнал,что некоторые начинаю- щие кодеры и представления не имеют о фла- гах и уж тем более об их использовании. Попал я как-то на корабль, где был фла- жок с черепком и костяшками ;). Флаг устанавливается для пометки выпол- нения определённого условия,т.е.даёт инфо- рмацию о только что выполненном вычисле- нии. В нашем Speccy, вернее, в центральном процессоре Z80, есть специальный регистр F (флаговый), который хранит в себе шесть флагов: 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | S | Z |###| H |###|P/V| N | CY| +---+---+---+---+---+---+---+---+ Флаг считается установленным, если в соответствующем бите записана единица, и сброшенным, если записан ноль. Z(zero) - флаг нуля. Устанавливается, если результат операции равен нулю. P/V(parity/overflow) - флаг чётности/пере- полнения. Этот флаг для некоторых команд действует как флаг чётности,а для других - как флаг переполнения.Флаг выступает в ро- ли указателя чётности при логических опе- рациях и устанавливается в том случае,если в результате установленным оказывается чё- тное количество битов.Переполнение говорит вам, что после выполнения арифметического действия изменился знак операнда,причём не при переходе через 0! CY(carry) - флаг переноса. Говорит, что в итоге арифметической операции или же при сдвиге регистра (см. ниже) произошёл пере- нос. Например при сложении чисел 240 и 58 дожно получиться 298, но восьми разрядных регистров хватает только на диапазон [0; 255], поэтому реально мы получим 42, а сташий девятый бит перейдёт во флаг C. S(sign) - флак знака. Он устанавливается, если при выполнении арифметической или ло- гической операции получился отрицательный результат. N(negative) и H(half-carry) - используются внутри процессора при проведении двоично- десятичной коррекции при работе с BCD-ари- фметикой. Надо сказать, что далеко не все команды микропроцессора оказывают какое-то влияние на флаги,а некоторые команды изменяют лишь отдельные биты флагового регистра. 3 "Условные и безусловные переходы" В системе команд процессора имеется не одна группа инструкций переходов, а целых две. Одна из них универсальна и позволяет переходить на любые расстояния,вторая слу- жит для более коротких переходов на расто- яния, не превышающие 126-129 байт. Первый тип инструкций обозначается JP(Jump - пры- гнуть), второй обозначается JR (Jump Rela- tive - относительный переход). Безусловные переходы: JR METKA JP 12345 JR #6001 JP #89AB JR $+10 JP METKA При первой же встрече процессором с по- добной командой он перейдёт по заданному адресу без каких-либо колебаний. Условные переходы: Эти команды также начинаются с JP и JR, но перед параметром - адресом перехода пи- шется соответствующее условие (проверка одного из флагов), при выполнении которого пограмма перейдёт к требуемому адресу. В противном случае программа просто будет работать дальше. ;Переход по заданному адресу выполняется.. JR C,addr ;если перенос (установлен флаг переноса CY) JR NC,addr ;если нет переноса (флаг пе- реноса CY сброшен) JR Z,addr ;если ноль (установлен флаг нуля Z) JR NZ,addr ;если не ноль (флаг нуля Z сброшен) JP C,addr ;если перенос (установлен флаг переноса CY) JP NC,addr ;если нет переноса (флаг пе- реноса CY сброшен) JP Z,addr ;если ноль (установлен флаг нуля Z) JP NZ,addr ;если не ноль (флаг нуля Z сброшен) JP M,addr ;если отрицательный результат (установлен флаг знака S) JP P,addr ;если результат положительный (флаг знака сброшен) JP PE,addr ;если чётность или переполне- ние (установлен флаг P/V) JP PO,addr ;если нет чётности/переполне- ния (флаг P/V сброшен) Для флагов H и N условия отсутствуют, так как они используются только в неявном виде командами коррекции двоично-десятич- ных чисел. Но при большом желании можно проверить и их,делается это так: PUSH AF POP DE ;/HL/BC BIT 1,E ;для флага N или BIT 4,E ;для флага H JR NZ,addr ;переход,если включен Хотя в своей практике я ещё никогда ими не пользовался... ;)... Ред: Я тоже... Глава 3 "ВЫВОД ТЕКСТОВЫХ СООБЩЕНИЙ" 4 "Вывод через команду RST 16" Ознакомимся с простейшим выводом текс- товых сообщений при помощи команд RST, но сначала узнаем их цели. Предусмотрены они были для быстрого и удобного вызова часто используемых подпро- грамм, которые расположены в нижних облас- тях памяти,то бишь в ПЗУ. RST 0 - иницилизация компьютера (переход к адресу 0). После её вызова компьютер сбро- сится и выйдет в Бейсик. RST 8 - вызов процедуры обработки ошибок. После её выполнения в системную переменную ERR_NR(23610) закинут код ошибки,указатель стека вытащат из переменной ERR_SP (23613/ 14),и с него (со стека) возьмут адрес про- граммы обработки ошибок.Изменив адрес,хра- нящийся в переменной ERR_SP, можно подру- бить собственные прогзы обработки ошибок бейсика (зачем?). RST 16 - программа выводит в текущий поток символ,код которого записан в аккумулятор. Куда будет производится печать - на экран или на принтер - зависит от того,какой по- ток открыт в данный момент.Прогза,устанав- ливающая текущим поток,номер которого ука- зан в аккумуляторе, расположена по адресу 5633. поток 0 и 1 - вывод на служебный экран. поток 2 - вывод на основной экран. поток 3 - вывод на принтер. RST 24 - программа выполняет при работе интерпретатора прием очередного символа, на который указывает системная переменная CH_ADD(23645/46),и производит его проверку на принадлежность к печатным. RST 32 - процедура выполняет последовате- льный просмотр символов вплоть до конца Бейсика. RST 40 - инициализирует встроеный медлен- ный калькулятор компьютера. О нём я здесь писать не буду, уж больно он медленный. Но если AlCo пустит слезу,то,может быть,уроке эдак в двадцатом я о нем расскажу;). RST 48 - процедура служит для создания до- полнительных ячеек памяти калькулятора. на каждую ячейку выделяется по пять байт. Пе- ред вызовом RST 48 в BC должно находиться суммарное количество байтов, которое нужно выделить для памяти калькулятора.После вы- полнения RST 48 в DE будет адрес начала выделенной области. RST 56 - процедура обработки прерывания. Процедура выполняет две задачи: опрос кла- вки и изменение текущего значения таймера в переменной FRAMES (23672/73/74).Код пос- ледней нажатой клавиши заносится в пере- менную LAST_K (23560). Если нажата новая клавиша или пришло время автоповтора ста- рой,в 5-й бит переменной FLAGS (23611) за- писывается единица. Кстати,команды RST NN можно заменить на команды CALL NN. Результат будет тем же,но CALL занимает 3 байта и дольше работает. Ну,приступим, собственно,к выводу текс- товых сообщений. Как вы уже узнали,для ис- пользования команды RST 16, нужно открыть поток для вывода на основной экран, т.е. в аккумулятор занести двойку (отличники мо- гут заносить другую цифру;),но т.к.комп не может угадать, куда вам нужно кинуть сооб- щение (в какие координаты), для этого были задуманы управляющие и цветовые коды. При- веду только те,которые нам понадобятся: код #16,X,Y - AT X,Y (аналог Бейсика;) код #10,N - INK N,где N - это цвет (0-7) код #11,N - PAPER N Текст и эти данные мы будем вводить че- рез директиву DEFB (она же DB). Попробуем же напечатать текстовое сооб- щение: LD A,2 ;код потока CALL 5633 ;открываем поток LD HL,TXT ;HL=адрес текста LD B,END-TXT ;длина текста PT LD A,(HL) ;берём код RST 16 ;и выводим INC HL ;следующий код DJNZ PT ;повторяем,пока не кончится RET TXT DB #16,10,5,#10,6,#11,1,"TEXT" END Этот способ не совсем оптимален,главный его недостаток - строгое ограничение текс- та є 256 байт. Чтобы избавиться от этого недостатка, нужно взять один из неиспользуемых кодов (часто это 255, 128 или 0). Он будет у нас маркером конца текста,и потому должен рас- пологаться в его конце. В самой процедуре мы организуем провер- ку каждого кода на предмет маркера,и,когда он будет найден,печать остановится. LD A,2 CALL 5633 LD HL,TEXT L0 LD A,(HL) CP 128 ;проверка на маркер конца текста RET Z ;если это он,то выход RST 16 INC HL JP L0 TEXT DB #16,10,5,#10,6,#11,1,"TEXT",128 5 "Прямой вывод в экран" Теперь попробуем обойтись без команды RST 16 и подпрограммы 5633 (открытие пото- ка). А будем выводить сообщения напрямую в экран. Но,прежде чем вывести сообщение, попро- буем вывести одну букву,например,"A". В памяти она будет хранится как 8 байт, каждый из которых хранит в себе значение 8 горизонтальных битов. Байты считаются так: 128+64+32+16+08+04+02+01= ------- +--+--+--+--+--+--+--+--+ ---- byte 1: | | |##|##|##|##| | | =060 ------- +--+--+--+--+--+--+--+--+ ---- byte 2: | |##|##|##|##|##|##| | =126 ------- +--+--+--+--+--+--+--+--+ ---- byte 3: | |##|##| | |##|##| | =102 ------- +--+--+--+--+--+--+--+--+ ---- byte 4: | |##|##| | |##|##| | =102 ------- +--+--+--+--+--+--+--+--+ ---- byte 5: | |##|##|##|##|##|##| | =126 ------- +--+--+--+--+--+--+--+--+ ---- byte 6: | |##|##|##|##|##|##| | =126 ------- +--+--+--+--+--+--+--+--+ ---- byte 7: | |##|##| | |##|##| | =102 ------- +--+--+--+--+--+--+--+--+ ---- byte 8: | |##|##| | |##|##| | =102 ------- +--+--+--+--+--+--+--+--+ ---- Выведем один байт в верхний левый угол, адрес которого соответствует началу экран- ной области,т.е. #4000 (16384). Вывести один байт можно командами: LD HL,#4000 ;адрес вывода LD (HL),byte ;значение или LD A,byte ;значение LD (#4000),A ;выводим Но нам нужно вывести восемь таких байт, и значит,адрес вывода с каждым последующим байтом нужно опускать на пиксел ниже. По таблице из первой главы этого урока видно, что это можно сделать командой INC H,кото- рая увеличивает старший байт адреса на 1, т.е. к числу в HL прибавляет 256: #4000+ +#100=#4100 LD B,8 ;высота буквы LD HL,#4000 ;адрес вывода LD DE,LETTER ;адрес восьми байтов буквы PT LD A,(DE) ;берём один байт LD (HL),A ;выводим его на экран INC H ;на пиксел опускаем адрес вывода INC DE ;переходим к следующему байту DJNZ PT ;повторяем восемь раз. RET LETTER DB #3C,#7E,#66,#66 ;байты буквы DB #7E,#7E,#66,#66 Надо заметить,что при таком способе вы- вода атрибуты экрана никоим образом не по- ртятся, а чтобы изменить цвет буквы, нужно рассчитать соответствующий адрес в атрибу- тах (в нашем случае это адрес #5800) и за- нести туда значение цвета. LD A,#48 ;значение атрибута LD (#5800),A ;подсвечиваем знакоместо с ;адресом в основном экране,равным #4000 Кто-то наверное уже смекнул, что, чтобы выводить сообщение, в памяти нужно держать полный символьный набор/шрифт/фонт. Добрый дядька Синклер приберёг в ПЗУ один из та- ких наборов по адресу 15360, но так как вы можете взять любой другой шрифт и размес- тить его в любом удобном вам месте, то ад- рес шрифта будем брать из переменной CHARS (23606/07). После иницилизации компьютера туда заносят адрес ПЗУ'шного шрифта,равный 15360.Но т.к.символы с кодами 0-31 - упра- вляющие, и в шрифте не определяются,то ад- рес действительного расположения на 256 байт (32*8) больше,чем указан в переменной CHARS. Так что если вы хотите изменить шрифт и подгрузили его на адрес NNNN, то включить его можно так: LD HL,NNNN-256 ;адрес шрифта-256 LD (23606),HL ;ложим в переменную CHARS Изменить шрифт можно и для первого спо- соба вывода (через RST 16), делается это так же. В нашей программе мы будем использовать стандартный шрифт, где каждый символ в ши- рину и высоту равен 8 пикселам, а в памяти хранится как 8 упорядоченных байт. Всего в стандартном шрифте 96 символов*8=768 байт. В следующем уроке я раскажу как работать с шрифтами:4x8,6x8,... Чтоб расчитать адрес того или иного си- мвола в шрифте, нужно его порядковый номер умножить на 8 (командами ADD HL,HL) и при- бавить к полученному числу адрес шрифта LD L,A ;в порядковый номер LD H,0 ;обнуляем старший байт ADD HL,HL ;x2 ADD HL,HL ;x4 ADD HL,HL ;x8 LD BC,(23606) ;берём адрес шригта ADD HL,BC ;+шрифт Теперь попробуем вывести ОДИН символ (в дальнейшем будем называть байты,хранящие в себе отрывок изображения,СПРАЙТОМ), спрайт которого расположен в шрифте,а адрес шриф- та лежит в переменной CHARS. LD DE,#4000 ;адрес вывода LD A,"A" ;код (порядковый) символа ;-------- PT ADD A,A ;x2 LD L,A ;переносим из в LD H,0 ;/ ADD HL,HL ;x4 ADD HL,HL ;x8 LD BC,(23606) ;берем адрес шрифта ADD HL,BC ;+шрифт LD B,8 ;высота символа L0 LD A,(HL) LD (DE),A INC HL INC D DJNZ L0 RET Ну и наконец-таки выведем целое сообщение: LD HL,TEXT LD DE,#4000 ;адрес вывода L1 LD A,(HL) ;берём код символа OR A ;проверяем на маркер конца (0) RET Z ;если это он,то выход PUSH HL PUSH DE CALL PT ;вызов прогзы печати символа POP DE POP HL INC HL ;следующий код INC E ;следующее знакоместо JP L1 PT .... TEXT DB "",0 ;<0> - маркер Но и тут есть одна проблема:опять длина текста не может превысить 256 байт, хотя в экран свободно влазит 768 (256*3). Это по- лучается благодоря тому,что команда INC E, которая увеличивает адрес вывода (адрес знакоместа),может достичь 255 и переполни- ться,после чего E будет равно 0,т.е.начнё- тся заново.Чтобы избежать этого,необходимо её заменить на такие команды: INC E ;следующее знакоместо JR NZ,L1 ;если не переполнил,то повтор LD A,D ;в противном случае увеличиваем ADD A,8 ;старший байт на 8,т.е. перехо- LD D,A ;дим к следующему сегменту. Надеюсь,что всё понятно!!! 6 "Вывод букв вытянутых в высоту" Третьим способом у нас будет вывод тек- стовых сообщений высотой в два символа.Для программы используется тот же шрифт, что и для второго способа, но будем при выводе растягивать его по вертикали. В первых двух способах не было проблем для расчёта адреса байта лежащего на пик- сел ниже,т.к. выводимые символы не залази- ли за грань одного знакоместа,и потому ис- пользовалась лишь одна команда INC H,кото- рая "опускает" адрес только внутри одного знакоместа.Ну а поскольку наш символ будет высотой в 2 знакоместа,то придётся следить за переходом из одного знакоместа в дру- гое. Но не пугайтесь сильно:прогзы,которые следят за этими переходами придуманы уже давно (и не спрашивайте, кто их придумал,я сам не знаю) и отлично справляются с этой работой. Процедуры, пересчитывающие адрес в HL или в DE на байт вниз(DHL и DDE) и на байт вверх(UHL и UDE). DHL DDE UHL UDE INC H INC D LD A,H LD A,D LD A,H LD A,D DEC H DEC D AND 7 AND 7 AND 7 AND 7 RET NZ RET NZ RET NZ RET NZ LD A,L LD A,E LD A,L LD A,E ADD A,32 ADD A,32 SUB 32 SUB 32 LD L,A LD E,A LD L,A LD E,A RET C RET C RET C RET C LD A,H LD A,D LD A,H LD A,D ADD A,-8 ADD A,-8 SUB -8 SUB -8 LD H,A LD D,A LD H,A LD D,A RET RET RET RET Я надеюсь,понять процедуру растягивания символа в высоту будет нетрудно.Просто ка- ждый последующий байт нужно выводить сто- лько раз, во сколько вы хотите увеличить символ. LD DE,#4000 LD A,"A" ;-------- PT ADD A,A LD L,A LD H,0 ADD HL,HL ADD HL,HL LD BC,(23606) ADD HL,BC LD C,8 ;высота символа L0 LD B,2 ;коэффициент растяжения L1 LD A,(HL) LD (DE),A CALL DDE ;адрес на байт ниже DJNZ L1 INC HL ;следующий байт буквы DEC C JR NZ,L0 RET Подпрога выводит только одну бук- ву,а чтобы вывести сообщение,нужно исполь- зовать программу из предыдущего параграфа. В этом способе есть одно НО: низя, чтоб сообщение залезло за край экрана, т.е.мак- симальная длина текста 32 буквы. Ну а теперь задачки: ? Попробуйте объяснить работу подпрограмм пересчёта адреса вверх/вниз. ? Попробуйте сделать вывод символа через строчку,т.е.как бы состоящего из 8 горизо- нтальных полосок. 7 "Вывод символов двойной ширины" Вывод довольно сложен,так что старайтесь читать внимательно,а то не въедете;). Для вывода нам потребуются команды ро- тации битов,но так как Alone Coder не опи- сал их в своих двух первых уроках,придётся это делать мне. Командами ротации вы можете сдвигать содержимое регистров A,H,L,B,C,D,E,(IX+n), (IY+n),(HL) влево,вправо - как вам угодно. Сложность состоит в том,чтобы различать различные сдвиги и циклические сдвиги с тем, чтобы знать,какие и когда нужно испо- льзовать, и помнить,что в их работе участ- вует флаг переноса (CARRY), который можно рассматривать в качестве девятого бита. ------------------------------------------ рег: A,B,C,D,E,H,L,(HL),(IX+n),(IY+n) 7 рег 0 -говорит о том,что данный байт из- меняется так, что все его биты скролятся влево (...>0>1>2>3>4>5>6>7>...). ------------------------------------------ Команды ротации: +-+ +-------+ RLC рег |C| -+|7 рег 0| -+ +-+ |+-------+ | +-----------+ +-------+ +-+ RRC рег +- |7 рег 0|+- |C| | +-------+| +-+ +-----------+ +-+ +-------+ RL рег +-|C| -|7 рег 0| -+ | +-+ +-------+ | +-----------------+ +-------+ +-+ RR рег +- |7 рег 0|- |C|-+ | +-------+ +-+ | +-----------------+ +-+ +-------+ +-+ SLA рег |C| -|7 рег 0| -|0| +-+ +-------+ +-+ +-+ +-------+ +-+ SRL рег |0|- |7 рег 0|- |C| +-+ +-------+ +-+ +-------+ +-+ SRA рег +- |7 рег 0|- |C| | +-------+ +-+ | | +---+ Команды ротации аккумулятора можно за- менять другими командами, которые работают быстрее и занимают всего один байт. RLC A = RLCA RRC A = RRCA RL A = RLA RR A = RRA SLA A = ADD A,A Теперь приступим к объясняловке вывода символов двойной (и более) ширины. Изнача- льно (без растяжения) символ будет успешно влазить и в одно знакоместо, но после рас- тяжения (в 2 раза),он,соответственно,будет занимать 2 знакоместа: 76543210 76543210 76543210 +--------+ +--------+--------+ | | 0 | | | | #### | 1 | ####|#### | | ## ## | 2 | #### | #### | | #### | 3 | ####|#### | | ## ## | 4 | #### | #### | | ## ## | 5 | #### | #### | | #### | 6 | ####|#### | | | 7 | | | +--------+ +--------+--------+ Нам всего лишь нужно продублировать ка- ждый бит всех восьми байтов.Продублировать байт побитно можно так: LD B,8 ;8 бит в одном байте LD A,%111100 ;байт в BIN системе PT1 RRCA ;берём состояние правого крайнего ;бита и пихаем его в флаг CARRY RR (HL) ;скидываем из CARRY в экран,а ;лишний ложим в CARRY INC L ;увеличиваем адрес вывода,т.к ;наш байт будет занимать два знакоместа RR (HL) ;CARRY->(HL) DEC L ;востанавливаем адрес SRA (HL) ;дублируем бит (см.выше) INC L RR (HL) DEC L DJNZ PT1 Hу теперь пользуясь этой процедуркой, выведем растянутый символ. LD A,"A" ;код символа LD DE,#4000 ;адрес вывода ;---------- PT ADD A,A ;x2 LD L,A ;A>HL LD H,0 ADD HL,HL ;x4 ADD HL,HL ;x8 LD BC,(23606) ADD HL,BC ;+FONT-256 EX DE,HL ;HL DE LD C,8 ;высота символа PT0 LD A,(DE) ;берём код LD B,8 PT1 RRCA RR (HL) INC L RR (HL) DEC L SRA (HL) INC L RR (HL) DEC L DJNZ PT1 INC DE INC H DEC C ;повтор,пока не кончится символ JR NZ,PT0 RET Глава 4 "ТАБЛИЦЫ" 8 "Очень хорошая и удобная оптимизация" Таблицы, которые изучают в школе,неско- лько отличаются от таблиц, используемых в программировании. Хотя,чем-то они схожи. Без таблиц практически невозможно напи- сать ни одной полноценной программы.Каждый программист/кодер должен уметь пользовать- ся таблицами. Приведу пример их применения...Блин,как на зло,ничего в голову не лезет.Ладно,сде- лаем так: у вас есть три адреса,содержимое которых нужно увеличить на 10. Тогда вы сделаете примерно так: LD A,(ADDR1) ;берём содержимое адреса ADD A,10 ;увеличиваем на 10 LD (ADDR1),A ;возвращаем LD A,(ADDR2) ADD A,10 LD (ADDR2),A LD A,(ADDR3) ADD A,10 LD (ADDR3),A Вах, пока заводил этот маразм, чуть не двинулся рассудком. Неужели так кто-то пи- шет? А если не 3 адреса,а 9? 20?... =8-[ ] Заменим этот бред таблицей: LD HL,TAB LD B,ENDTAB-TAB/2 ;длина таблицы L1 LD E,(HL) INC HL LD D,(HL) INC HL LD A,(DE) ADD A,10 LD (DE),A DJNZ L1 TAB DW ADDR1,ADDR2,...,ADDRn ENDTAB Нифига,самому понравилось!!! Хотя можно ещё лучше сделать: LD (ST+1),SP LD SP,ENDTAB-2 LD B,ENDTAB-TAB/2 L1 POP HL LD A,(HL) ADD A,10 LD (HL),A DJNZ L1 ST LD SP,0 Этот способ я зря наверное привёл, а то вы вон какие глаза выпучили, что мне аж страшно стало. Теперь всё поподробней... Как видите, адреса мы вынесли в отдель- ную строку - она, собственно, и называется таблицей. Причём там не обязательно должны быть адреса,там может находиться расчитан- ный синус,какие-нибудь коэффициенты,... Ну, вроде с самой таблицей разобрались, теперь объясню,как ей пользоваться.В нашем случае в таблице хранятся адреса,а они,как правило,занимают по 2 байта.Допустим,у нас есть адрес #ZXYV,который делится на 2 бай- та: #ZX (старший) и #YV (младший),но в па- мяти он (адрес) будет находиться как пос- ледовательность кодов #YV и #ZX,т.е.сперва младший, а потом старший. Так что,если нам нужно поместить этот адрес в DE,то мы пос- тупим так: LD HL,TAB ;адрес таблицы ... LD E,(HL) ;берём младший байт INC HL LD D,(HL) ;берём старший байт INC HL ;в DE получаем адрес Но, т.к. адрес у нас не один, организуем цикл: LD HL,TAB LD B,ENDTAB-TAB/2 L1 LD E,(HL) INC HL LD D,(HL) INC HL ;DE - адрес DJNZ L1 А допустим, вы не знаете длину таблицы. Тогда вам необходимо в конец таблицы пос- тавить маркер конца - число 0 (#0000). LD HL,TAB L1 LD E,(HL) INC HL LD D,(HL) LD A,D OR E ;проверяем на ноль RET Z ;если ноль,то выход ;DE - полученный адрес INC HL JP L1 _ TAB DW ADDR1,ADDR2,...,ADDRn,0 Команда OR E возвращает 0, только если оба операнда (A и E) были равны нулю. Если какой-нибудь из них не ноль,то прогза про- должит работу. Вроде,с такими таблицами разобрались,но как поступать с таблицами,в которых храня- тся не адреса, а какие-то однобайтные дан- ные? С ними ещё легче: LD HL,TAB LD B,ENDATB-TAB L1 LD A,(HL) INC HL ;A - данные DJNZ L1 ... TAB DB #XY,#FX,#NX,... или LD HL,TAB L1 LD A,(HL) OR A RET Z ;A - данные INC HL JP L1 _ TAB DB #XV,#XG,#GX,.. ...,0 Понятно!? Только не забывайте, что для адресов и вообше двухбайтных чисел ставит- ся директива DEFW (DW),а для однобайтных - DEFB (DB). Глава 5 "ПОЛЕЗHЫЕ СОВЕТЫ" 9 "Секреты Z80" Здесь вы узнаете некоторые наиболее из- вестные ухищрения программирования. 1.Аккумулятор будет уменьшаться, пока не станет равен нулю, дальнейшего уменьшения не последует. OR A |1b|4t SUB 1 |2b|7t JR Z,$+3 |2b|7/12t ADC A,0 |2b|7t DEC A |1b|4t --------+--+-- ---------+--+------ |4b|14t |4b|15/20t 2.переброска блока в цикле. LD A,(HL) |1b|7t LDI |2b|16t LD (DE),A |1b|7t ----+--+--- INC HL |1b|6t |2b|16t INC DE |1b|6t DEC BC |1b|6t ----------+--+--- |5b|32t 3.интересное использование флага переноса: -если флаг переноса (CARRY) включен (1), то A будет равно #FF (или HL = #FFFF). -если выключен (0),то A=0 (или HL=0). SBC A,A и SBC HL,HL. 4.запуск программы через раз. Первый раз обратились,прога запустилась, второй раз нет,третий да,и т.д. CYKLE .... CALL PROGQ .... JP CYKLE PROGQ LD A,0 XOR 1 LD ($-3),A RET Z PROG .... RET 5.изменение адреса возврата. LD HL,NN ;NN - новый адрес возврата EX HL,(SP) 6.деление аккумулятора на четыре. (последний случай работает,если A чётное) SRL A |2b|8t RRA |1b|4t OR A |1b|4t SRL A |2b|8t RRA |1b|4t RRA |1b|4t ------+--+-- AND #3F |2b|7t RRA |1b|4t |4b|16t --------+--+-- -----+--+-- |4b|15t |3b|12t 7.вычитание из HL LD HL,NN |..|.. LD HL,NN |..|.. LD BC,ZZ |..|.. LD BC,-ZZ |..|.. AND A |1b|4t ADD HL,BC |1b|11t SBC HL,BC |2b|15t ----------+--+-- ----------+--+--- |1b|11t |3b|19t 8.смачные переходы: а)переход,если бит 7 аккумулятора = 0 OR A или RLA JP PE,...0 JR NC,...0 ...1 ...1 б)переход,если бит 6 = 0 ADD A,A JP P,...0 ...1 в)переход,если бит 0 = 0 RRA JR NC,...0 ...1 г)переход,если прерывания отключены LD A,I JP PO,...imoff ...imon 9.проверка регистра на нуль. INC рег DEC рег 10.дополнение аккумулятора до 100 SUB 101 CPL Из журнала ZX-Guide#4, Рязань, 25.11.2001  Nikphe/Anarchia, Alone Coder/i8/Anarchia Урок ассемблера для ламеров. (продолжение) После изучения трёх первых уроков вы сильно приблизились к званию программиста, но чтобы стать хорошим кодером, вам необ- ходимо научиться оптимизировать свои (и не только) программы. --------------+ Если ты перестал| встречать трудности,| значит,ты сбился с пути.| --------------------------+ Глава 1 "СТЕК" 1 "Стек и его прямое предназначение" Стек - это хранилище, работа с которым ведётся по следующему принципу:элемент,за- писаный в стек последним, считывается из него первым. Для стека можно отвести практически лю- бую область памяти компьютера. Стек запол- няется сверху вниз: первый элемент записы- вается в самый конец области стека (в яче- йку области с наибольшим адресом), следую- щий элемент записывается "под" ним и т. д. При чтении же из стека первым всегда уда- ляется самый нижний элемент. Поэтому полу- чается,что верх стека фиксирован (это пос- ледняя ячейка области стека),а вот низ по- стоянно сдвигается.Как ни странно,низ сте- ка, несмотря на то, что он располагается в более низких адресах,называют вершиной.Для того, чтобы знать текущее положение этой вершины, используется регистр SP (stack pointer, указатель стека). В нём хранится адрес ячейки, в которой находится элемент, записанный в стек последним. Элементы стека могут иметь "любой" раз- мер;это могут быть и байты,и слова (2 бай- та), и если постараться,то и двойные слова (4 байта) и т.д.Однако имеющиеся в Z80 ко- манды записи в стек и считывания из него работают только со словами. Поэтому обычно подстраиваются под эти команды и считают, что элементы стека имеют размер слова. Об- работка же байтов и двойных слов подгоняе- тся под обработку слов. Имена ячейкам стека обычно не дают,т.к. доступ к ним всё равно будет осуществлять- ся не по именам, а косвенно, через регистр SP.Чаще всего не задают и начальные значе- ния для этих ячеек,но не всегда. Прежде чем начать работу со стеком, не забудьте установить в SP его вершину.В на- чале работы программы стек должен быть,как правило, пустым,в этом случае SP указывает на первую ячейку за областью стека. В бейсике стек можно сместить командой: CLEAR addr-1 Где addr - верхний адрес области стека. 2 "Стековые команды" Для работы со стеком имеется несколько команд,которые принято называть стековыми. Основными стековыми командами являются команды записи и считывания слов на стеке. Запись слова в стек: PUSH rp (rp (регистровая пара):HL,DE,BC,AF,IX,IY) Команда PUSH ("вталкивать") записывает в стек свой операнд. Условно это можно изоб- разить так: #0000 |##|<-sp #0000 |##| +--+ +--+ #FFFF |##| #FFFF |ст| +--+ +--+ #FFFE |##| #FFFE |мл|<-sp +--+ +--+ #FFFD |##| #FFFD |##| Более точно,команда PUSH действует так: сначала значение регистра SP сдвигается вниз, и теперь указывает на свободную яче- йку области стека,а затем в неё записывае- тся операнд. Чтение слова из стека: POP rp (rp:HL,DE,BC,AF,IX,IY) Команда POP ("выталкивать") считывает сло- во из вершины стека и присваивает его ука- занному операнду.Более точно:слово из яче- йки, на которую указывает регистр SP,пере- сылается в операнд,а затем SP увеличивает- ся на 2. #0000 |##| #0000 |##|<-sp +--+ +--+ #FFFF |ст| #FFFF |ст| +--+ +--+ #FFFE |мл|<-sp #FFFE |мл| +--+ +--+ #FFFD |##| #FFFD |##| Остальные команды работы со стеком: LD SP,addr -установка SP на адрес addr; LD SP,rp -установка SP на адрес, хранящийся в rp (HL,IX,IY); LD (addr),SP -сохранение SP по адресу addr; LD SP,(addr) -прочитать значение SP из ячейки с адре- сом addr; INC SP -смещение SP на один байт вверх; DEC SP -смещение SP на один байт вниз; EX (SP),rp -обмен числа с вершины стека и rp (HL,IX,IY); ADD rp,SP -прибавить SP к rp (HL,IX,IY); ADC HL,SP -сложение HL и SP с учётом переноса; SBC HL,SP -вычитание SP из HL с учётом переноса. 3 "Влияние некоторых команд на стек" Значительная часть глюков, появляющихся при написании программ,возникает в связи с неумелой работой со стеком. Команда вызова процедуры: CALL addr Эта команда работает через стек и экви- валентна последовательности: LD HL,label PUSH HL JP addr label .... Но не портит регистр HL.Т.е.полноценная замена команды CALL могла бы выглядеть так: PUSH HL LD HL,$+7 EX (SP),HL JP addr Таким образом,команда CALL сначала сох- раняет на стеке адрес возврата из процеду- ры,и только потом переходит в процедуру по адресу addr. Команда возврата из процедуры: RET Её можно приблизительно заменить на ко- манды: POP HL JP (HL) То есть команда RET берёт адрес с вер- шины стека и пo нему возвращается из подп- рограммы. 4 "Правильное использование стека" Собственно,использование стека уже зат- рагивалось и в первом и третьем уроках.Да, даже если внимательно прочитать вышеизло- женное, то и так будет ясно, как правильно работать со стеком. Так что затрону я сдесь наиболее важный момент: Например,вот так работать со стеком низя: LD BC,???? PUSH BC CALL label ..... RET label ..... LD BC,???? ..... POP BC ..... RET Так как в вызываемой подпрограмме label после команды POP BC вы получите не сохра- няемую ранее rp BC, а адрес возврата из процедуры, а по команде RET вернётесь не в исходную программу,а "неизвестно куда",по- сле чего вы рискуете потерять управление над компьютером. В подобных ситуациях лучше делать так: LD BC,???? LD (l1+1),BC CALL label ..... RET label ..... LD BC,???? ..... l1 LD BC,0 ..... RET Надеюсь, что теперь вы таких глюков ло- вить не будете ;)))). 5 "Очищение памяти через стек" Вы,надеюсь,знаете стандартную процедуру очистки памяти через LDIR: LD HL,addr ;адрес очищаемой области LD DE,addr+1 LD BC,len-1 ;len:длина этой области LD (HL),0 ;заполнение её нулём LDIR ; (очистка) Но эта процедура довольно медленна, и, например, за счёт неё, очистить экран за один фрейм (71680t) не удастся. Для этого можно воспользоваться стеком. Напомню,что команды LD HL,0 PUSH HL Заносят в память два нуля,т.е."очищают" её.То есть,если мы установим вершину стека на экранную область и повторим эти команды len/2 раз (т.к. заносится сразу по два ба- йта),то экран очистится: LD SP,#5800 LD HL,0 DUP len/2 PUSH HL EDUP RET Желаемого результата мы добились,но по- чему после очистки экрана программа повис- ла? Да потому, что адрес возврата, который мы сохраняли на стеке,безвозвратно потерян ;),так как мы переустановили вершину стека в экранную область. Спросите,как же быть?! А очень просто, нам достаточно перед вызо- вом программы сохранять предыдущее значе- ние стека,а после окончания работы восста- навливать: LD (stek+1),SP LD SP,#5800 LD HL,0 DUP len/2 PUSH HL EDUP stek LD SP,0 RET Есть ещё один наш недочёт. Так как наш экран занимает 6144 байта (без атрибутов), то такая комбинация команд DUP len/2 PUSH HL EDUP в памяти будет занимать 6144/2=3072 ба- йта (3072 команды PUSH HL), что приводит к опупенной неэкономии памяти (хотя после компресии она сильно ужмётся;). Если вам катастрофически мало памяти, то поступите так: LD (stek+1),SP LD SP,#5800 LD HL,0 LD B,192 cls0 DUP len/2/192 ;=16 PUSH HL EDUP DJNZ cls0 stek LD SP,0 RET Ну вот, теперь и скорость приемлема, и размер невелик. 6 "Вывод спрайтов через стек" В прошлом уроке я Вам рассказывал, как выводить спрайты размером 1x1, 1x2 и 2x1, причём все они были по высоте кратны одно- му знакоместу,т.е.кратны 8 байтам (если не забыли,что в знакоместе восемь байт;). Попробуем же вывести спрайт произволь- ного размера шириной не более 32 байт (не забыли,что ширина экрана 32 байта?;) и вы- сотой не более 192 байт ;). Спрайт расположим по адресу SPRITE. Вы- водить надо так,чтобы при выводе не произ- водить лишние вычисления и учитывать пос- ледовательность хранения данных спрайта. Обычно спрайт хранят так:сначала идут под- ряд байты спрайта,слева направо,первой ве- рхней строчки, затем,в такой же последова- тельности,второй строчки и т.д. %-]. Да,высота спрайта не обязательно должна быть кратна восьми (знакоместу!),что,впро- чем,предстоит выбирать вам. Сначала покажу,как нужно выводить через команду ldi: LD HL,SPRITE ;адрес спрайта LD DE,#4000 ;адрес вывода LD B,HGH ;высота спрайта в пикселах S0 PUSH DE LD C,L DUP LEN ;ширина спрайта в знакоместах LDI EDUP POP DE CALL DDE ;подрограмма расчитывания ад- ;реса в экране лежащего на ;пиксел ниже адреса взятого ;из DE (такие процедурки я ;приводил в третьем уроке) DJNZ S0 RET SPRITE DB ?,?,?,... ;сам спрайт Надо заметить,что команды LD C,L DUP LEN LDI EDUP поставлены не спроста, а для скорости, если для вас не важна скорость (всё может быть;),а её размер,то замените их на PUSH BC LD BC,LEN LDIR POP BC Но вот если вы хотите наоборот увели- чить скорость вывода,то для этого восполь- зуемся стеком. Метод,который я вам сейчас расскажу,ис- пользует команду POP. Учитывая все описанные свойства этой команды, мы можем установить вершину стека на адрес рассположения спрайта (LD SP,??). Выводить будем,как и в прошлый раз,в адрес #4000. Ну вот,собственно,и прогза: LD (STEK+1),SP ;не забываем сохранять ;прежнее значение стека LD SP,SPRITE ;устанавливаем на спрайт LD HL,#4000 ;адрес вывода LD B,HGH ;высота в пикселах ST LD C,L ;сохраняем L в C DUP LEN/2 ;длина в байтах кратна 2-м POP DE ;берём два байта LD (HL),E ;выводим сначала младший INC L LD (HL),D ;потом старший INC L EDUP ORG $-1 ;последний INC L не нужен LD L,C ;востанавливаем L из C INC H ; \ LD A,H ; | AND 7 ; | JR NZ,S0 ; | LD A,L ; | ADD A,32 ; +- (Down HL) LD L,A ; | JR C,S0 ; | LD A,H ; | SUB 8 ; | LD H,A ; / S0 DJNZ ST STEK LD SP,0 RET SPRITE DB ?,?,?,... Да,не забывайте,что ширина спрайта (LEN) всегда должна быть кратна двум,т.к. коман- да POP снимает сразу два байта! Ещё: при снятии байтов командой POP,вы- водить их надо на экран в таком порядке: сначала младший,а потом старший. Конечно, эта программа далеко не опти- мальна - для увеличения скорости ее работы желательно DOWN HL использовать только на каждой 8-й строке,а на остальных использо- вать INC H. Причем,если вы собираетесь вы- водить спрайт с точностью до пиксельной строки, то нужно предусмотреть вход в про- цедуру и выход из нее не только с начала, но и с любого другого места. (Этот метод называется DMD - Down Micro Dub.) Если хотите как-то навернуть програм- мку, то не забывайте,что в середине проце- дурки низя ставить CALL, PUSH, POP (кро- ме...),т.е. низя трогать стек... хотя,если умудриться, то можно кое-где схитрить,нап- ример, ещё раз сохранить состояние вершины стека и установить третье (это мне напоми- нает какую-то вложенность;). 7 "Вывод всего экрана за один фрейм" Собственно, это,наверно,последнее,что я хотел сказать по поводу стека ;).Это метод уже где только не описывался, но,к сожале- нию, не все его поняли,да и не все знают о его существовании. Хотя метод в самом деле очень лёгкий,и понять его не стоит большо- го труда. Статичную картинку легко вывести коман- дами LD BC,... PUSH BC (или с помощью DE, аналогично) записав их 3072 раза. При этом вместо мно- готочий в каждой команде должны стоять данные для выводимого экрана. Естествен- но, такая последовательность команд должна быть сгенерирована программно. Для написа- ния такой программы вам потребуется книжка с кодами команд.В дальнейшем будет предпо- лагаться, что такая книжка у вас есть (или вы умеете узнавать коды команд с помощью STS ;)), и что написание таких генераторов для вас не в новинку. А теперь поставим реальную задачу:выве- сти за прерывание экран, скроллируемый по вертикали. Допустим, попиксельно. Для этого нам нужно разбить выводилку на отдельные строки (назовём такую строку "кидалка"): DUP 16 LD BC,... PUSH BC EDUP а в промежутке между ними придётся модифи- цировать указатель стека (назовём такую процедуру "менялка"): INC H RRCA ;один бит рег.A установлен JR NC,$+8 -+ EX DE,HL | LD L,(HL) | INC HL | LD H,(HL) | INC HL | EX DE,HL | <-+ LD SP,HL или проще,но медленнее: EX DE,HL LD L,(HL) INC HL LD H,(HL) INC HL EX DE,HL LD SP,HL или быстрее, но с таблицей,разбросанной по страничке: SET 7,H LD SP,HL POP HL LD SP,HL или просто LD SP...,а параметр менять вне- шней процедурой. Это самый медленный, но экономный по памяти метод. Если скроллинг планируется познакомест- ный, то достаточно указывать такой кусок программы только на каждой 8-й строке,а на остальных: INC H LD SP,HL Теперь нужно выбрать, будет у нас пос- тоянная отображаемая картинка размером вы- ше экрана или же мы собираемся просто сдвигать экран и дорисовывать освободивши- еся строки. В первом случае мы будет иметь длинную последовательность "менялка" "кидалка" "менялка" "кидалка" ... в сумме занимающую чуть больше 2 байт на каждый выводимый байт. Но её можно оптими- зировать как по длине, так и по скорости, заменив все вхождения LD DE,0 PUSH DE (если у нас кидалка работает через DE) на PUSH BC или как-нибудь более интересно, например, переприсваивая только тот регистр, который изменился: LD D,24 PUSH DE и т.п. Вызов нашей последовательности будет осуществляться с любой "менялки" или "ки- далки",как вам удобнее. Перед вызовом (че- рез JP), естественно,надо поставить в нуж- ном месте точку выхода - например,JP (IX). При вызове нужно также установить в HL, A, DE (или что у нас там требуется для "меня- лки"?) значения, соответствующие верху эк- рана. При установке JP (IX) нужно запомнить, что раньше было в этих 2 байтах, а после выполнения выводилки восстановить их. Находить адреса входов и выходов лучше всего по табличке,которая должна быть пос- троена во время генерации выводилки. Второй случай используется так: в конце выводилки стоит переход к ее началу. А скроллинг происходит из-за изменения точки входа и выхода в процедуру (это, собствен- но, одна и та же точка;)) Освободившиеся при скроллинге строки нужно заполнять,но не на экране! С экраном пускай работает выводилка,а мы будем поме- щать данные в неё. Так как обычная единица информации - знакоместная строка, а она в выводилке занимает 512 байт (или чуть бо- льше), удобно использовать для печати,ска- жем, символов, регистры IX и IY, где IY на 256 больше, чем IX: POP DE LD C,E LD L,D LD A,(BC) OR (HL) LD (IX-128),A INC B INC H LD A,(BC) OR (HL) LD (IX-94),A INC B INC H ... и т.д. Вывод экрана сверху вниз ведёт к появ- лению глюка, называющегося "юла" или "кле- шинг" - когда электронный луч, выводящий экран, обгоняет нашу выводилку. Бороться с этим надо так:вызывать выво- дилку не целиком,а: сперва вывести верхнюю половину экрана, потом HALT, потом нижнюю половину экрана. Это несложно. ------------------------------------------ Если ты уже было совсем забросил писать свою программу,так как не можешь и не уме- ешь найти в ней глюк,то прочитай это - до- лжно помочь... Самые частые ошибки и опечатки в программах начинающих кодеров: (указаны только отдельные примеры,в ко- торых может встретиться ошибка.Разумеется, вариант с другим регистром или другим нап- равлением инкремента/декрамента более чем вероятен :)) 1. В конструкции LD A,(HL) INC HL пропущен INC HL Проявления: не может заполниться какая-то табличка,программа не выходит из цикла или что-то в этом роде. Поиск: обычно видно уже на первом проходе цикла при трассировке. 2. Вместо LD BC,#10ff написано LD B,#10ff (видно безо всякой трассировки) 3. Программа активно использует стек, но прерывания забыли отключить. Проявления: повисание; или через некоторое время в памяти портится какая-то табличка; или на экране появляются загадочные пиксе- ли;иногда никак не проявляется - например, в случае POP: LD с постоянно обновляемой табличкой, а также когда программа помеща- ется в прерывание. Поиск: BreakPoint на программу со стеком,и смотрим состояние прерываний. Не помогает, если программа вызывается регулярно,а пре- рывания включает кто-то другой при сложном событии (обычно - при обращении к диску). 4. Включен IM 1, но программа использует регистр IY. Проявляется обычно как сброс/вис при дис- ковых операциях. Происходящий, причём, не всегда! Ох, как меня достал этот глюк ;) (не лечится: включите IM 2 без обращения к RST 56 и восстанавливайте IY перед диско- выми операциями,либо вообще не используйте IY) 5. В конце программы допущен ORG, не имею- щий отношения к адресу запуска программы. Проявления: программа не запускается. Поиск: после ассемблирования заходим в STS и видим этот ORG. (лучший совет:поставь в точке запуска про- граммы метку GO, а последней строкой прог- раммы сделай ORG GO) 6. Вместо INC L JR NZ,... написано INC L DJNZ ... (или наоборот) Проявления: табличка занимает места больше или меньше положенного;возможно повисание. Поиск: трассировка цикла.Ошибка выявляется на первом или втором проходе.