(C) PSB/Halloween
Прямое программирование
General Sound.
Всем привет! Решил я написать немно-
го о кульной спектрумовской звуковой кар-
точке, а также о ее программировании. Но
только не думайте, что это очередная ста-
тья о том, как играть MOD'ы и эффекты или
озвучивать игры... Подобной литературы в
Спектрумовской прессе не было (или я про-
сто не видел :-! ).
Начну с самых истоков. Году в 1997 я
приобрел GS (кстати, спасибо за это Hell-
Raiser/Halloween), и как, наверное, мно-
гие, стал учиться его программить, играл
те же MOD и FX, озвучивал геймы, написал
свой player MOD (который понимал и TR-DOS
и MS-DOS ). Но все это потихоньку подна-
доело, ведь разнообразие-то небольшое. А
ведь когда GS еще только рекламировали,
писали, что это - еще один комп, со своим
процом, памятью и частотой раза в 3 выше.
Так оно и есть. А главное, проц-то - ува-
жаемый всеми нами Z80!!!
Многие, наверное, до сих пор думают,
как можно программировать GS напрямую,
если в описании нет таких команд? Где лю-
ди берут информацию по недокументирован-
ным командам и вообще, как устроен GS?
Думаете есть какие-нить доки по этому по-
воду? Сейчас - ДА! Сейчас уже даже схему
GS по кусочкам разобрали, а раньше всего
этого не было, но узнать все равно было
можно и даже просто. Опишу как это сделал
я (по-любому, кому-то это будет интерес-
но).
В старые добрые времена была тради-
ция (можно и так сказать) драть отовсюду
AY'шные музоны. Ну меня и прибило дернуть
MOD из Target Renegade и Xecutor. Дерну-
ть-то дернул, а вот в Xecutor в загрузчи-
ке нашел тест GS. Он помимо стандартной
информации еще выдавал и копирайты из
прошивки. Рассмотрев повнимательней про-
гу, которая все это достает из карточки
(там несколько разных кусочков достава-
лось), прикинул, где может задаваться ад-
рес, из которого это все берется... ну
взял и набрал похожую прогу в MASM, толь-
ко она у меня тянула с адреса #0000 пер-
вые 32кб. А потом, посмотрев через STS
(Z80 ведь!), что получилось, увидел прог-
рамму! Все правильно, это было ПЗУ. Ну а
дальше понятно: стал потихоньку разби-
рать, что и как, нашел главный цикл, ко-
торый обрабатывает поступающие команды,
посмотрел, как работают документированные
команды, из них узнал о некоторых портах,
как выводится звук и так далее. Несколько
экспериментов, и я знал уже практически
все, задолго до появления подобной инфор-
мации. И это все благодаря 3-м(!!!) кома-
ндам из загрузчика Xecutor !
Хорошо, предположим, знаем мы Gene-
ral Sound "изнутри", а что нам это дает?
Да очень многое, точнее - использование
платы нестандартным образом. Это может
быть все, что угодно, начиная от дописы-
вания процедур, которых не хватает в ста-
ндартной прошивке, до своих программ, ко-
торые, может быть, и к музыке-то не отно-
сятся.
Вот так. А сейчас, собственно, о
программировании.
Интерфейс со стороны Speccy
1. Command register (#BB=187, запись).
2. Status register (#BB=187, чтение).
Биты: 7 - Data bit*
0 - Command bit*
3. Data register (#B3=179, запись).
4. Output register (#B3=179, чтение).
*) Вообще, не помню точно, как описыва-
лись значения этих битов после определен-
ных операций в стандартной инструкции
(типа, когда в какой-то порт что-то пишем
или из него читаем, биты как-то меняют-
ся), но могу сказать проще - БИТ УСТАНОВ-
ЛЕН В 1, ЕСЛИ ОН _НЕ_ОБСЛУЖЕН_. То есть,
если мы что-нить кинем в Data register,
то Data bit будет в 1, пока GS из него не
прочитает. То же самое будет и внутри GS
- пока ZX не прочтет порт, в Data bit бу-
дет 1.
Особенности описания команд
SC @$%&%$ - Послать код команды @$%&%$ в регистр команд
WC - Ожидание принятия/выполнения команды
(сброса Command bit)
SD @$%&%$ - Послать данные @$%&%$ в регистр данных
WD - Ожидание принятия данных (сброса Data bit)
GD @$%&%$ - Принять данные @$%&%$ из регистра данных
WN - Ожидание новых данных от GS (установки Data bit)
Команды
#18 - LD DE,nnnn
SD nnnn_LOW
SC #18:WC
SD nnnn_HIGH
Заносит в рег. пару DE значение nnnn
(внутри GS!). nnnn_LOW и nnnn_HIGH - ста-
рший и младший байты значения nnnn.
#1A - Get data from (DE)
SC #1A:WC
GD Value
Читает байт из ячейки, адресуемой
DE.
#1B - INC DE
SC #1B:WC
Увеличивает значение DE на 1.
Таким образом, уже используя только
эти 3 команды, вы можете прочитать память
GS и узнать все, что надо:
LD HL,#0000; адрес в GS, откуда хотим что-то прочитать
LD A,L:CALL SD; делаем LD DE,nnnn в GS
LD A,#18:CALL SC
LD A,H:CALL SD
LD HL,#8000; адрес в ZX, куда все будем складывать
LD DE,#8000; длина блока
LOOP LD A,#1A:CALL SC; берем значение из GS
IN A,(#B3)
LD (HL),A
LD A,#1B:CALL SC; увеличиваем адрес в GS
INC HL:DEC DE
LD A,D:OR E:JR NZ,LOOP
RET
SC OUT (#BB),A
WC IN A,(#BB):RRCA:JR C,WC
RET
SD OUT (#B3),A:RET
WD IN A,(#BB):RLCA:JR C,WD
RET
WN IN A,(#BB):RLCA:JR NZ,WN
RET
Но есть и ряд других полезных (прос-
то необходимых) команд.
#14 - Put datablock to GS
SD LENG_LOW
SC #14:WC
SD LENG_HIGH:WD
SD GS_ADR_LOW:WD
SD GS_ADR_HIGH:WD
SD DataByte:WD ; столько раз подряд, сколько указано в LENG
Засылает блок данных в GS по адресу
GS_ADR и длиной LENG. Этой командой очень
удобно загружать свои программы в GS.
#13 - Jump to ADR
SD ADR_LOW
SC #13:WC
SD ADR_HIGH
Переходит по адресу ADR в GS. Подхо-
дит для запуска загруженной программы.
#10 - OUT (PORT),A
SD PORT
SC #10:WC
SD Value
#11 - IN A,(PORT)
SD PORT
SC #11:WC
GD Value
Эти две команды позволяют прочитать
или записать значение в порт/из порта GS.
Есть еще куча всяких полезных и бес-
полезных команд (аля всякие ковоксы и
т.п.), но для прямого программинга они
нам не потребуются.
Интерфейс со стороны General Sound
Внутренние порты:
#00, запись - открыть нужную страничку памяти
#01, чтение - прочитать содержимое регистра команд
#02, чтение - прочитать содержимое регистра данных
#03, запись - заслать данные для Speccy
#04, чтение - прочитать содержимое регистра состояния
#05, запись - сброс бита Command bit в регистре состояния
#06, запись - громкость канала A
#07, запись - громкость канала B
#08, запись - громкость канала C
#09, запись - громкость канала D
Есть еще порты #0A и #0B, но они
вообще нафиг не нужны и не используются
(хотя, кто хочет, по схеме может посмот-
реть, что они делают ;).
А теперь подробнее...
1. OUT (#00),Page (открыть нужную страни-
чку памяти) В General Sound расположение
памяти такое:
#0000-#3FFF - первые 16к ПЗУ
#4000-#7FFF - 16к ОЗУ
#8000-#FFFF - страницы ОЗУ
Т.е. странички в GS по 32 килобайта
и располагаются с адреса #8000. В нулевой
странице лежит ПЗУ целиком (т.е. по адре-
сам #8000-#BFFF тоже, что и с
#0000-#3FFF, а с #C000 - продолжение). В
первой странице вторые 16к - копия памяти
с #4000-#7FFF, а первые 16к - обычные.
Вторая, третья и т.д. страницы - обычные
страницы, которые можно свободно исполь-
зовать. В базовом варианте GS (128к) 3
странички: 2, 3 и 4, а у GS512 - 14 стра-
ниц (не учитывая 1-ю и 0-ю). Короче, чем
больше памяти, тем больше страниц. В
GS_ROM есть программа тестирования памяти
(после reset), так вот там вроде задано
аж 64 страницы...
И еще по поводу памяти. На 512-ти
килобайтной версии есть такой аппаратный
"глюк": при включении одной из страниц,
включаются сразу две, т.е. 32к пропадают.
Поэтому всего у GS доступной памяти
51232=480к!
2. IN A,(#01) (прочитать содержимое реги-
стра команд) Т.е. прочитать, что было по-
слано Спеком в регистр команд (#BB).
3. IN A,(#02) (прочитать содержимое реги-
стра данных) Т.е. прочитать, что было по-
слано Спеком в регистр данных (#B3).
4. OUT (#03),Data (заслать данные для
Speccy) По сути, передать данные для Спе-
ктрума, т.е. их можно будет прочитать на
ZX из регистра данных (#B3).
5. IN A,(#04) (прочитать содержимое реги-
стра состояния)
Биты: 0 - Command bit
7 - Data bit
Назначение их такое же, как и у Sta-
tus register со стороны Speccy. Например,
если Command bit равен 1, значит поступи-
ла команда и надо бы ее выполнить.
Примечание: при работе с регистром данных
(чтение/запись), Data bit _автоматически_
изменяется, в отличие от Command bit! Ес-
ли со Спектрума послали число в регистр
команд, то Command bit, как и положено,
установится в 1, но он не сбросится при
чтении его GS'ом.
6. OUT (#05),A (сброс бита Command bit в
регистре состояния) Обычно этим дается
знать компу, что команда в GS выполнена
(или принята к обслуживанию). Число засы-
лаемое в порт может быть любым.
Примерчик (алгоритмик) работы со всем этим:
> Ждем пока Command bit не установится в
1. Этим мы дожидаемся когда со Спека пос-
тупит команда.
> Берем значение из регистра команд (но-
мер команды). В зависимости от этого пры-
гаем куда надо.
>...прыгнули, допустим, сюда... Необходи-
мо дать понять Спеку, что команда приня-
та, чтобы он не повисал в ожидании. Т.е.
делаем OUT (#05), а затем выполняем нуж-
ную прогу. Либо можно сделать иначе, если
что-то нужно выполнить, а затем передать
результаты компу: сначала все выполняем,
запихиваем данные с помощью OUT (#03), a
затем делаем OUT (#05). На Speccy надо
будет дать номер команды, дождаться ее
выполнения и смело брать данные из Output
register. Вообще, здесь необходимо дейст-
вовать логично, и если вы сделаете OUT
(#05) раньше, чем OUT (#03), то рискуете
взять на Спектруме не те данные. Ну да
мне кажется, что такие моменты и так дол-
жны быть ясны...
7. OUT (#06),Volume (громкость канала A)
OUT (#07),Volume (громкость канала B)
OUT (#08),Volume (громкость канала C)
OUT (#09),Volume (громкость канала D)
Громкости задаются числом от 0 до
63(#3F).
Вывод в ЦАП:
Он сделан весьма оригинально: по
чтению из памяти. Так, сначала надо запи-
сать нужное число в ячейку, а затем про-
читать его оттуда. Канал (точнее сказать
ЦАП), зависит от адреса:
#6000-#60FF - для канала A
#6100-#61FF - для канала B
#6200-#62FF - для канала C
#6300-#63FF - для канала D
#6400-#64FF - для канала A
#6500-#65FF - для канала B
#6600-#66FF - для канала C
#6700-#67FF - для канала D
И т.д. до #7FFF.
Эта область памяти удобно может быть
использована под буфер, т.е. вы сначала
быстро кидаете в нее данные, а потом в
нужное время воспроизводите.
Прерывания:
Частота сигнала INT - 37500Гц, т.е.
прерывания приходят 37,5 тыс. раз в секу-
нду. На них обычно и вешают проигрывалку
музы, точнее, прогу, которая кидает дан-
ные в ЦАП'ы.
Прикинем, 12000000/37500=320 тактов
на прерывание. Это не так много, поэтому
надо рассчитывать, чтобы то, что висит на
прерываниях, не было слишком долгим, ина-
че основная прога будет сильно тормозить.
Практика
Здесь будут рассмотрены конкретные
примеры по программированию GS на ASM'е.
1. Определение наличия GS.
;CY=1, если GS отсутствует
GS_DET LD A,#7F:IN A,(#FE):CPL; самый удачный вариант -
RRCA:RET C; принудительное отключение user'ом.
LD A,#F3:OUT (#BB),A; restart GS
LD B,200; время (ZX_INTs), в течение которого ждем
; ответа от GS - 4 сек.
GS_DET1 EI:HALT:DI
IN A,(#BB):RRCA; смотрим, взял ли GS команду
IN A,(#7B):JR NC,GS_DET2
;in a,(#7b) нужен для того, чтобы прога не вылетала на Penta-
;gon'ах со включенным ZX-LPRINT3, т.к. если GS нет,то включит-
;ся ПЗУ ZX-LPRINT3!
DJNZ GS_DET1
SCF:RET; GS так и не ответил
GS_DET2 XOR A:OUT (#BB),A; след. этап - команда Reset Flags.
GS_DET3 EI:HALT:DI
IN A,(#BB):CP #7E; проверяем, сбросились ли флаги...
IN A,(#7B):RET Z
DJNZ GS_DET3
SCF:RET; время вышло, а флаги не сбросились
;Хотя... если вдруг из (#BB) всегда читается 0 (платы нет), то
;тест ошибется... Тогда надо бы (а надо ли вообще?) добавить
;тестов, например, заставить GS вернуть заданное вами значение
;(команда #10, порт 3).
2. Загрузчик программ в GS.
INITgs CALL DEINIT; restart GS, если еще не делали
LD HL,SUBPRG; адрес нашего вспомогательного загрузчика
LD BC,#0080; длина блока
LD A,C:CALL SD
LD A,#14:CALL SC; загрузить блок в GS
LD A,B:CALL SD:CALL WD
XOR A:CALL SD:CALL WD; адрес загрузки #4000
LD A,#40:CALL SD:CALL WD
LOOPiGS LD A,(HL):CALL SD:CALL WD; засылаем блок
INC HL:DEC BC
LD A,B:OR C:JR NZ,LOOPiGS
XOR A:CALL SD; запускаем программу с адреса #4000
LD A,#13:CALL SC
LD A,#40:CALL SD
;А дальше работает наш загрузчик, который подготовит все для
;загрузки нашей программы. Зачем такой изврат? Сначала, когда
;работало внутреннее ПО карты, стек был в районе #4400, а с
;#4000 шли всякие системные переменные этого ПО. А наш ма-
;ленький загрузчик переставит стек и, если надо, включит нуж-
;ную нам страницу и т.п.
LD HL,PROG; адрес и длина самой программы
LD BC,EPRG-PROG_
LOOpiGS LD A,(HL):CALL SD:CALL WD; засылаем блок
INC HL:DEC BC
LD A,B:OR C:JR NZ,LOOpiGS
RET
;А вот и сам загрузчик. Учитываем, что он должен работать по
;адресу #4000, поэтому либо ассемблируем его соответственно,
;либо не используем прямых адресаций (JP, CALL)
SUBPRG DI
LD SP,#407F
LD HL,#4080:PUSH HL; адрес загрузки в GS
LD C,2; порт
LD DE,EPRG-PROG_
SUBPRG1 IN A,(4):BIT 7,A:JR Z,SUBPRG1; ждем прихода байта
INI:DEC DE; кидаем его в память
LD A,D:OR E:JR NZ,SUBPRG1
RET
;Далее запускается наша прога. Шаблон такой:
PROG
ORG #4080,$
PROG_ .....
.....
EPRG
;org #4080,$ - это синтаксис STORM'а, а как это делается в
;других ассемблерах, вам лучше знать. Смысл такой, что прог-
;рамма физически располагается следом за основной, а ассембли-
;рована под адрес #4080.
3. Поиск свободной памяти в GS.
LD HL,#FFFF
LD A,#0F; максимум 15 страниц...
LPP0 OUT (0),A:LD (HL),A; в страницу записываем ее номер
DEC A:CP 1:JR NZ,LPP0; все страницы кроме 1-й и 0-й
;Можно, конечно, и 1-ю посчитать, но работать с ней не очень
;удобно (аля 5-й банк в Speccy).
INC A
LD DE,PAGETAB; таблица номеров страниц
LD IX,0; будем считать количество страниц
LPP1 OUT (0),A
CP (HL):JR NZ,LPP2
LD (DE),A; если номер страницы совпадает с числом,
INC DE,HX; записанным в ней, то заносим ее в таблицу
LPP2 INC A:BIT 6,A:JR Z,LPP1; 15-я - последняя
;Дальше, если надо, можем очистить страницы. Но желательно
;учесть, что страниц может быть много, и лучше бы (если память
;позволяет) очищать через PUSH.
.....
;Можно передать информацию о количестве страниц Спеку.
LPP3_ LD A,HX:OUT (3),A
OR A:JP Z,0; если страниц нет, то RESET
IN A,(4):RLCA:JR C,$-3; ждем пока он их возьмет
.....
PAGETAB DS 14
4. Основной цикл и процедуры.
;Вариантов построения программы несколько. Если предполагается
;наличие небольшого количества команд, то можно просто поль-
;зоваться условиями CP #XX:JR Z,NNNN. Если же команд много, то
;лучше составить табличку с адресами.
GSMAIN1 IN A,(4):RRCA:JR NC,GSMAIN1; ждем команду
IN A,(1); берем ее номер
LD HL,GSMAIN1:PUSH HL; по RET вернемся в цикл
OR A:JR Z,PROG0; команда 0
CP 1:JR Z,PROG1; команда 1
CP 2:JP Z,PROG2; команда 2
CP #F3:JP Z,0; стандартные RESET'ы
CP #F4:JP Z,0
;если команда отсутствует, то ничего не делаем, а просто дадим
;Спектруму знать, что команда принята, а то он повиснет в
;ожидании
OUT (5),A
RET
;Вариант программы, когда сначала сигналим Спектруму, что все
;ОК, а затем исполняем прогу.
PROG0 OUT (5),A
.....
RET
;Вариант программы, когда сначала что-то исполняем, получаем,
;затем посылаем результат Спектруму, а потом уже сигналим, что
;прога выполнилась.
PROG1 .....
OUT (5),A
RET
;А можно и так выпендриться.
PROG2 OUT (5),A
.....
IN A,(4):RRCA:JR NC,$-3; ждем прихода любой команды
.....
OUT (5),A
RET
;Т.е. здесь будет так: вы посылаете со Спектрума команду #2,
;GS говорит, что команда принята, что-то исполняет и ждет при-
;хода любой команды. Потом опять что-то исполняет, и говорит,
;что все готово. Получается некий триггер.
5. Загрузка/выгрузка блоков данных.
а) Вариант 1 - коротко и ясно.
;Загрузка данных (GS side):
LD HL,#8000; адрес в GS
LD BC,#4000; длина блока
WAITD IN A,(4):RLCA:JR NC,WAITD; ждем байт
IN A,(2):LD (HL),A:INC HL; принимаем
DEC C:JR NZ,WAITD; повторяем
DEC B:JR NZ,WAITD
;Выгрузка (GS side):
LD HL,#8000; адрес в GS
LD BC,#4000; длина блока
WAITD_1 LD A,(HL):OUT (3),A; высылаем байт
WAITD_ IN A,(4):RLCA:JR C,WAITD_; ждем принятия
INC HL
DEC C:JR NZ,WAITD_1; повторяем
DEC B:JR NZ,WAITD_1
;Загрузка данных в GS (ZX side):
LD HL,#8000
LD BC,0-#4000; (0 минус длина блока)
GS1 LD A,(HL):OUT (#B3),A; кидаем
IN A,(#BB):RLCA:JR C,$-3; ждем принятия
INC C:JR NZ,GS1
INC B:JR NZ,GS1
;Выгрузка данных из GS (ZX side):
LD HL,#8000
LD BC,0-#4000
GS2 IN A,(#BB):RLCA:JR NC,$-3; ждем поступления
IN A,(#B3):LD (HL),A; принимаем
INC C:JR NZ,GS2
INC B:JR NZ,GS2
б) Вариант 2 - ускоренный за счет раскры-
тия циклов, но при этом должна быть фик-
сированная длина блока.
;Загрузка данных (GS side):
LD HL,#8000; адрес в GS
LD E,8; количество циклов
LD C,2; порт
LOAD_2
.0 IN A,(4):RLCA:JP NC,$-3:INI; повторяем 256 (0) раз
DEC E:JP NZ,LOAD_2; 256*8=2048 байт примем
;Загрузка данных в GS (ZX side):
LD HL,#8000
LD C,#B3; порт данных
LD E,8; количество циклов
LOAD_2_
.0 OUTI:IN A,(#BB):RLCA:JP C,$-3; повторяем 256 (0) раз
DEC E:JP NZ,LOAD_2_; 2048 байт передадим
;Смысл понятен - раскрываем циклы, тем самым жрем память, но
;немного повышаем быстродействие. Остальные процедуры, надо
;будет, сами напишете... Кстати, цикл ожидания тоже можно не-
;много развернуть:
;было
IN A,(#BB):RLCA:JP C,$-3; 11+4+10=25 тактов
;стало
LOOOP
IN A,(#BB):RLCA:JR NC,LOOOPE; 11+4+7=22 такта
IN A,(#BB):RLCA:JR NC,LOOOPE
IN A,(#BB):RLCA:JR C,LOOOP
LOOOPE
;Т.к. jr при невыполнении условия длится 7 тактов, то мы эко-
;номим по 3 такта на цикл. Т.е. опрос происходит чаще.
в) И еще один не менее интересный спо-
соб:
;Загрузка данных (GS side):
LD HL,#8000; адрес
LD C,1; порт
IN A,(4):RLCA:JP NC,$-3;
INI; +повторяем нужное кол. раз
IN A,(2):LD (HL),A:INC HL; /
OUT (5),A;чтобы потом GS не подумал,что пришла команда
;Таким образом, прием идет через 2 порта. Т.е. через порт 1
;(#BB на ZX) идет первый байт, а через порт 2 (#B3) - второй.
;Загрузка данных в GS (ZX side):
LD HL,#8000; адрес
LD C,#BB; порт
OUTI;
LD A,(HL):OUT (#B3),A:INC HL; +повт. нужное кол. раз
IN A,(#BB):RLCA:JP C,$-3; /
;Воспользоваться данным методом для передачи данных из GS не-
;возможно, т.к. в этом направлении порт только один.
6. Вывод звука.
а) Проигрывание сэмпла из памяти без пре-
рываний.
LD HL,#8000; адрес сэмпла в GS
LD BC,#8000; длина сэмпла
LD DE,#6000; адрес ячейки ЦАП (#61XX, #62XX, #63XX)
LOOP LD A,(HL); берем байт
LD (DE),A; заносим в память
LD A,(DE); кидаем в ЦАП
.....; некоторая задержка (EI:HALT или NOP'ы ?!?)
INC HL:DEC BC
LD A,B:OR C:JR NZ,LOOP
б) Проигрывание сэмпла из памяти, с испо-
льзованием прерываний.
;программа, висящая на прерываниях, может выглядеть так:
;DE=SMP_ADR
INT PUSH HL,AF
LD HL,#6000; адрес ЦАП'а
LD A,(DE):INC DE; берем очередной байт сэмпла
LD (HL),A:LD A,(HL):INC H; ЦАП A
LD (HL),A:LD A,(HL):INC H; ЦАП B
LD (HL),A:LD A,(HL):INC H; ЦАП C
LD (HL),A:LD A,(HL); ЦАП D
POP AF,HL
EI:RET
;Естественно, что проигрывание не будет остановлено - нет та-
;кого условия, т.к. это только пример. Причем плохой. Прерыва-
;ние не должно быть слишком длинным, поэтому желательно убрать
;оттуда лишние команды, да и вообще, организовать работу по-д-
;ругому. Сделать область памяти #6000-#60FF и т.д. буфером, в
;который мы быстро напихаем данные, а потом они будут играться
;со своей скоростью. А когда закончатся, снова напихаем!
;HL'=#6000
INT EXX:EXA; EXA=EX AF,AF'
LD A,(HL); кидаем в ЦАП
INC L:JR Z,FILLBUF; если буфер кончился...
EXA:EXX
EI:RET
FILLBUF EXX:EXA
EI
FB1 LD HL,#8000; адрес сэмпла
LD BC,256; длина буфера
LD DE,#6000; адрес буфера
LDIR
LD (FB1+1),HL; сохраняем след. адрес
RET
;Несмотря на то, что FILLBUF вызывается из прерываний и раз;-
;решает их, ничего страшного не произойдет, если успеть все
;сделать, пока буфер не закончился опять. Также надо успеть
;закинуть первый байт до того, как придет прерывание, иначе
;проиграется не то и будет щелчок. Выходом из этого положения
;является создание 2-го буфера, таким образом, пока один иг-
;рается, заполняем второй.
;Кстати, частота дискретизации в этом случае будет 37500Гц,
;поэтому, если вам надо меньше, то придется изменить алгоритм
;поставить делители частоты (в FILLBUF), которые заставят один
;и тот же байт повторяться несколько раз.
7. Отладка.
В связи с отсутствием отладчика под
GS, написанные программы нелегко отлажи-
вать. Но можно воспользоваться тем, что
GS умеет воспроизводить звук, отследив
тем самым этап, на котором прога глючит.
Напишем пищалку:
GSBEEP LD BC,#0010,DE,#6000
GSBEEP1 LD A,120:LD (DE),A,A,(DE)
DJNZ $
LD A,128:LD (DE),A,A,(DE)
DJNZ $
DEC C:JR NZ,GSBEEP1
;сюда можно вставить паузу, на случай, если 2 бипа пойдут че-
;рез небольшое время
RET
Теперь можно навставлять в программу
обращений к GSBEEP и слушать, сколько би-
пов будет до сбоя...
Найти ошибку в алгоритме можно с по-
мощью STS. Просто нужно программу для GS
поместить в память ZX-Spectrum (по тем же
адресам) и пошагово исполнять, только ко-
манды in a,(xx) пропускать, а в аккумуля-
тор заносить нужные значения вручную (out
(xx),a можно просто пропускать).
Кстати, на данный момент уже появи-
лась низкоуровневая эмуляция GS в
Z80Stealth, там же есть и отладчик, так
что можно пользоваться им, правда, вот,
только не очень удобно...
Ну вот пока и все. Надеюсь, я ясно
все об'яснил и показал. Теперь любой че-
ловек, знающий ассемблер Z80, может легко
разобраться с прямым программированием
GS. Кто знает, может скоро появится куча
хороших программ под GS ? Никто не хочет
сделать дебагер для Speccy с ядром в GS?
;-)))