ZXNet эхоконференция «code.zx»


тема: Программирование задержек на ассемблере Z80



от: Ivan Roshin
кому: All
дата: 02 Apr 2002
Hello, All! ═══════════════════ wait_1 .W ══════════════════ (c) Иван Рощин, Москва Fido : 2:5020/689.53 ZXNet : 500:95/462.53 E-mail: asder_ffc@softhome.net WWW : http://www.ivr.da.ru Программирование задержек на ассемблере Z80 ═══════════════════════════════════════════ ("Радиомир. Ваш компьютер" 3/2002) При написании программ для низкоуровневой работы с внешними устройствами часто возникает необходимость запрограммировать задержку на определенное время. Для этого можно либо использовать таймер (если он есть), либо вставить в программу фрагмент, время выполнения которого равно требуемому времени задержки. Первый способ я здесь рассматривать не буду, а вот о втором расскажу подробнее. Каждая команда процессора Z80 выполняется за определенное количество тактов. Зная величину требуемой задержки в тактах, можно написать фрагмент программы с соответствующим временем выполнения. (Сразу же замечу, что работать такой фрагмент должен при запрещенных прерываниях.) Формула для вычисления времени задержки в тактах: N=F*t где N - количество тактов, F - тактовая частота процессора, t - время задержки. Так как время выполнения команды в процессоре Z80 может выражаться лишь целым числом тактов, полученный результат следует округлить до ближайшего целого числа. Пример: пусть нужна задержка в 5 мс, тактовая частота - 3,5 МГц. Тогда требуемое количество тактов равно: 5*10^-3 с * 3,5*10^6 Гц = 17500. Начнем с программирования небольших задержек. Минимальная величина задержки соответствует наименьшему времени выполнения команды Z80 - 4 такта. Для задержки на 4 такта идеально подходит команда NOP - "нет операции". Ее выполнение не зависит от значения флагов и регистров, и она не выполняет каких-либо действий (иначе говоря, не имеет побочных эффектов). С помощью цепочки NOP'ов легко получить задержку на 8, 12, 16,... тактов (т.е. на число вида 4N). Задержку на 5 тактов можно получить с помощью команды условного выхода из подпрограммы в случае, когда условие не выполняется. Таких команд восемь: RET NC (выход при C=0) RET C (выход при C=1) RET NZ (выход при Z=0) RET Z (выход при Z=1) RET P (выход при S=0) RET M (выход при S=1) RET PO (выход при P/V=0) RET PE (выход при P/V=1) Побочных эффектов они не имеют. Добавляя NOP'ы, можно получить задержку на 9, 13, 17,... тактов (т.е. на число вида 4N+1). Очевидно, для использования одной из вышеперечисленных команд необходимо знать состояние хотя бы одного из четырех флагов (C, Z, S или P/V) к моменту ее выполнения. Если состояние флагов неизвестно, то задержку на 5 тактов получить нельзя, но на 9, 13, 17,... - можно. Для этого надо вначале установить флаг переноса с помощью команды SCF (время ее выполнения - 4 такта), после чего использовать команду RET NC, а затем при необходимости добавить NOP'ы. Установка флага переноса будет в этом случае побочным эффектом. Задержку на 6 тактов можно получить с помощью одной из следующих команд: INC BC DEC BC INC DE DEC DE INC HL DEC HL INC SP DEC SP Добавляя NOP'ы, можно получить задержку на 10, 14, 18,... тактов (т.е. на число вида 4N+2). Вышеуказанные команды не влияют на флаги, однако изменяют содержимое соответствующей регистровой пары. Если ни одну из четырех пар изменять нельзя, то и задержку на 6 тактов получить нельзя. Но на 10, 14, 18,... тактов - можно, с помощью команды безусловного перехода на следующую команду (JP $+3), на выполнение которой тратится 10 тактов, и добавленных при необходимости командах NOP. Задержку на 7 тактов можно получить различными способами. Один из них - использование команды LD r,(HL), где r - один из регистров A,B,C,D,E,H,L. Эта команда не влияет на флаги, но изменяет содержимое участвующего в операции регистра. Если ни один регистр изменять нельзя, но изменять флаги можно, то годится команда CP (HL). Если нельзя изменять ни регистры, ни флаги, - можно использовать команду условного перехода JR (при невыполняющемся условии). Таких команд четыре: JR NC (переход при C=0) JR C (переход при C=1) JR NZ (переход при Z=0) JR Z (переход при Z=1) Естественно, чтобы одну из них можно было использовать, необходимо знать состояние флага C или Z на момент ее выполнения. Добавляя NOP'ы, можно получить задержку на 11, 15, 19,... тактов (т.е. на число вида 4N+3). ════════════════════════════════════════════════ С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Apr 2002
Hello, All! ═══════════════════ wait_2 .W ══════════════════ Итак, как же, основываясь на вышесказанном, построить участок программы, обеспечивающий нужную задержку? Пусть x - величина задержки в тактах. Разделим x на 4, получим частное a и остаток b. Тогда b+4 - это длина первой команды (4, 5, 6 или 7 тактов), а a-1 - количество добавляемых NOP'ов. Рассмотрим это на примерах. Пример 1: x=18. Делим на 4: a=4, b=2. Значит, длина первой команды будет 2+4=6 тактов, а количество NOP'ов будет 4-1=3. Соответствующий фрагмент программы будет выглядеть так: INC HL NOP NOP NOP Пример 2: x=29. Делим на 4: a=7, b=1. Значит, длина первой команды - 5 тактов, количество NOP'ов - 6. Как уже ранее упоминалось, задержка на 5 тактов реализуется условным RET'ом, а для его применения надо знать состояние хотя бы одного из четырех флагов. Предположим, что о состоянии флагов ничего не известно. Тогда придется сделать так: с помощью последовательности команд SCF: RET NC получаем задержку в 9 тактов, а NOP'ов будет на один меньше. В результате получим: SCF RET NC NOP NOP NOP NOP NOP Так как, вообще говоря, NOP'ов при таком программировании задержки может получиться довольно много, то, чтобы не "раздувать" текст программы и не ошибиться в подсчете NOP'ов при наборе текста, удобно использовать директиву ассемблера DS N (другой вариант записи - DEFS N). Она выделяет участок памяти длиной N байтов и (обычно) заполняет его нулями. Ну а 0 - это как раз и есть код команды NOP. Только убедитесь, что используемый вами ассемблер заполняет область действительно нулями. Если это не так, придется указывать нулевое значение явно: DS N,0. Таким образом, рассмотренные выше примеры могут быть записаны так: INC HL DS 3 и SCF RET NC DS 5 Если вас не устраивает, что длинная цепочка NOP'ов занимает много места в памяти, то можно использовать следующий фрагмент программы: LD B,N DJNZ $ Время его выполнения - 13N+2 тактов (где N=1..255). Таким образом, если нужна задержка на x тактов, делим x-2 на 13, получаем частное a и остаток b. Значение a показывает, сколько раз надо повторить цикл (это число, загружаемое в регистр B), а b - сколько тактов при этом остается (задержку на это время придется реализовать обычным способом). Если b=1, 2 или 3, то, так как задержку на это время получить нельзя, придется уменьшить a на 1 и увеличить b на 13 (тем самым увеличив оставшуюся задержку до 14, 15 или 16 тактов). Пример: x=140. Делим на 13: a=10, b=8. Следовательно, количество повторов цикла будет 10; задержку на оставшиеся 8 тактов обеспечиваем с помощью двух команд NOP. Получаем: LD B,10 DJNZ $ DS 2 ════════════════════════════════════════════════ С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Apr 2002
Hello, All! ═══════════════════ wait_3 .W ══════════════════ Теперь перейдем к рассмотрению способов реализации более длительных задержек. Для задержек от 188 до 65535 тактов удобно использовать следующую 98-байтную процедуру: ORG #8000 ;или другой адрес, кратный 256 TAB_ADR DB ADR_0256 DB ADR_1256 .............. DB ADR_31256 ADR_28 NOP ADR_24 NOP ADR_20 NOP ADR_16 NOP ADR_12 NOP ADR_8 NOP ADR_4 NOP ADR_0 NOP ;4 такта RET ADR_29 NOP ADR_25 NOP ADR_21 NOP ADR_17 NOP ADR_13 NOP ADR_9 NOP ADR_5 NOP ADR_1 RET NZ ;5 тактов RET ADR_30 NOP ADR_26 NOP ADR_22 NOP ADR_18 NOP ADR_14 NOP ADR_10 NOP ADR_6 NOP ADR_2 INC HL ;6 тактов RET ADR_31 NOP ADR_27 NOP ADR_23 NOP ADR_19 NOP ADR_15 NOP ADR_11 NOP ADR_7 NOP ADR_3 LD A,(HL) ;7 тактов RET WAIT LD DE,-156 ADD HL,DE LD A,L AND 31 LD E,A ;Чтобы разделить HL на 32, достаточно сдвинуть HL на 5 разрядов ;вправо, но есть более короткий и быстрый способ: сдвинуть HL на ;3 разряда влево, помещая выдвигаемые старшие биты результата в ;аккумулятор, и затем произвести перестановку: A -> H -> L. XOR A ADD HL,HL RLA ADD HL,HL RLA ADD HL,HL RLA LD L,H LD H,A ;Цикл с временем выполнения 32 такта: WAIT_1 DEC HL LD A,H OR L NOP ;для NOP ;задержки JP NZ,WAIT_1 EX DE,HL LD H,TAB_ADR/256 LD L,(HL) JP (HL) Напомню, что оператор "" - это вычисление остатка от деления. В вышеприведенной процедуре он используется, чтобы получить младший байт двухбайтной величины. Если в используемом вами ассемблере этот оператор не поддерживается, то, возможно, предусмотрен специальный оператор вычисления младшего байта, или же ассемблер сам отбрасывает старший байт, когда результат должен быть однобайтным. В крайнем случае можно воспользоваться равенством A256=A-((A/256)*256). ════════════════════════════════════════════════ С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Apr 2002
Hello, All! ═══════════════════ wait_4 .W ══════════════════ Требуемая продолжительность задержки в тактах (от 188 до 65535) указывается в регистровой паре HL перед вызовом процедуры. При этом не нужно специально учитывать, что выполнение команд загрузки этого числа в HL и вызова процедуры тоже займет какое-то время: это уже учитывается в самой процедуре. Если, скажем, в каком-то месте программы нужна задержка в 500 тактов, просто пишем следующее: LD HL,500 CALL WAIT Как видим, применение этой процедуры не требует от программиста специальных знаний о времени выполнения команд. Не нужно каждый раз думать, какой последовательностью команд реализовать ту или иную задержку. Ставим нужное число и все! Проще не бывает! Также этой процедурой очень удобно пользоваться, если точная величина задержки заранее не известна, и приходится "подгонять" ее в процессе отладки программы. Изменить время задержки можно, изменив лишь одно число в коде программы с помощью отладчика. При своей работе процедура изменяет значения регистровых пар HL и DE, а также аккумулятора и регистра флагов. Если это нежелательно, то, в зависимости от ситуации, можно либо сохранять их в стеке и затем восстанавливать (командами PUSH, POP), либо на время выполнения процедуры устанавливать альтернативный набор регистров, если там не содержится ничего нужного (командами EXX, EX AF,AF'). При этом из указываемого в HL времени задержки надо будет вычесть время выполнения команд сохранения и восстановления регистров (следите, чтобы при этом указываемое время задержки не стало меньше минимального - 188 тактов!). Напомню: время выполнения команд PUSH HL, PUSH DE, PUSH AF - 11 тактов; POP HL, POP DE, POP AF - 10 тактов; EXX, EX AF,AF' - 4 такта. Пример: пусть нужна задержка в 750 тактов, и значения HL и DE не должны портиться (альтернативные регистры - тоже). Значит, HL и DE надо сначала сохранить в стеке (PUSH HL, PUSH DE), а после вызова процедуры - восстановить (POP DE, POP HL). Подсчитываем время выполнения этих команд: 11+11+10+10=42 такта. В программе пишем: PUSH HL ;сохранение PUSH DE ;регистров LD HL,750-42 CALL WAIT POP DE ;восстановление POP HL ;регистров Так как процедура WAIT - универсальная, то в случае единичного ее использования она займет, очевидно, больше места в памяти, чем специально написанный для реализации конкретной задержки фрагмент программы. Но если процедура вызывается во многих местах программы, с различными временами задержки, то это будет уже выгоднее по сравнению с тем, как если бы в каждом месте был свой фрагмент программы, реализующий задержку. Процедуру WAIT можно было бы, в принципе, сделать короче и не привязывать к адресу, кратному 256. Но я при ее написании стремился к тому, чтобы минимальная задержка, достигаемая с ее помощью, была как можно меньше, т.е. к увеличению области использования процедуры. ════════════════════════════════════════════════ С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Apr 2002
Hello, All! ═══════════════════ wait_5 .W ══════════════════ Ниже приведен текст еще одной процедуры, для реализации более продолжительных задержек (от 469 до 2^32-1 тактов). WAIT_LONG PUSH BC PUSH AF LD BC,-405 ADD HL,BC LD BC,-1 EX DE,HL ADC HL,BC EX DE,HL LD A,L AND 3 ADD A,A ADD A,A ;*4 ADD A,WAIT_CODE256 LD C,A LD A,WAIT_CODE/256 ADC A,0 LD B,A PUSH BC LD A,L RRA RRA CPL AND 15 ADD A,WAIT_NOP256 LD C,A LD A,WAIT_NOP/256 ADC A,0 LD B,A PUSH BC ;Сдвигаем DEHL: XOR A ADD HL,HL EX DE,HL ADC HL,HL EX DE,HL RLA ADD HL,HL EX DE,HL ADC HL,HL EX DE,HL RLA LD L,H LD H,E LD E,D LD D,A ;В DE - количество повторов цикла (каждое - 64 такта). LD BC,-1 WAIT_L1 ADD HL,BC INC L ;Проверяем: L=0? DEC L ;(не изменяя флаг C). JP Z,WAIT_L2 EX DE,HL ADC HL,BC EX DE,HL JR WAIT_L1 WAIT_L2 LD A,H ;L=0, а HDE=0? OR D ;Флаг C сбрасывается. OR E NOP ;Для NOP ;задержки RET C ;в 4+4+5=13 тактов. JP NZ,WAIT_L1 RET ;Переход по адресу, ранее ;помещенному в стек. WAIT_NOP DS 15,0 ;15 команд NOP. RET ;Переход по адресу, ранее ;помещенному в стек. WAIT_CODE NOP JP WAIT_EXIT RET C JP WAIT_EXIT INC HL JP WAIT_EXIT LD A,(HL) JP WAIT_EXIT WAIT_EXIT POP AF POP BC RET Длина процедуры 119 байтов. Требуемая продолжительность задержки в тактах указывается так: старшие разряды загружаются в регистровую пару DE, младшие - в HL. Как и в предыдущей процедуре, время выполнения команд загрузки и вызова специально учитывать не нужно. Пример: пусть нужна задержка в 100000 тактов. В шестнадцатеричном виде это #186A0. В программе пишем: LD HL,#0001 LD DE,#86A0 CALL WAIT_LONG ════════════════════════════════════════════════ С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Apr 2002
Hello, All! ═══════════════════ wait_6 .W ══════════════════ Если в программе уже используется процедура WAIT для задержек, меньших 469 тактов, а величина необходимой задержки не во много раз превосходит 65535, то можно обойтись без процедуры WAIT_LONG (тем самым сэкономив в размере программы), просто поставив рядом несколько вызовов процедуры WAIT. Пример: нужна задержка в 150000 тактов. Делим 150000 на 65535, получаем частное 2 и остаток 18930. В программе пишем: LD HL,65535 CALL WAIT LD HL,65535 CALL WAIT LD HL,18930 CALL WAIT Напоследок отмечу, что процедуры WAIT и WAIT_LONG не содержат самомодифицирующихся участков; следовательно, возможна их прошивка в ПЗУ. ════════════════════════════════════════════════ С уважением, Иван Рощин.




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Посмеемся - тараканщина: женщина и таракан.
Pain - Slip "Я не пытаюсь вые..ться! Просто делаю игры. Делаю их, одну за другой"...
Iron Making - схема подключения Kempston Mouse.
Гости - Презентация группы JURASSIC SOFT из г.Ульяновска.
Форум - Уважаемые читатели - пишите письма!

В этот день...   8 мая