Историческая эволюция дисковых накопителей в мире Spectrum
Контекст эпохи магнитных лент
Оригинальный ZX Spectrum 48K использовал обычный кассетный магнитофон для загрузки и сохранения данных со скоростью примерно 1500 бит/сек. Среднее время загрузки игры составляло 3-5 минут, что создавало спрос на более быстрые носители.Первые попытки внедрения дисководов
Первые дисковые интерфейсы для Spectrum появились в 1983-1984 годах:- Mikro+ (MGT) — первый коммерчески успешный интерфейс
- Beta Disk (MGT) — усовершенствованная версия, ставшая стандартом
- Disciple (Romantic Robot) — альтернативное решение
- Plus D (MGT) — интерфейс для принтера и дисковода
- Opus Discovery — британский интерфейс с двумя дисководами
TR-DOS: рождение стандарта
Файловая система TR-DOS (Tape Recording DOS) была создана в 1984 году компанией Technology Research Ltd (позже MGT) и стала де-факто стандартом для клонов Spectrum в Восточной Европе и СССР.Аппаратная архитектура Beta Disk Interface
Технические характеристики
Код:
Процессор: WD1793 или совместимый (например, КР1818ВГ93 в СССР)
Тактовая частота: 4 МГц
Поддержка дисководов: до 4 (обычно 1-2)
Типы дисководов: 5.25" (реже 3.5")
Емкость дискеты: 640 КБ (80 дорожек × 16 секторов × 256 байт × 2 стороны)
Интерфейс с Z80: через порты ввода-вывода
Память: собственный ROM 16 КБ, иногда дополнительный RAM
Схема подключения и адресация
Assembler Z80:
; Основные порты Beta Disk
BETA_REG equ $1FFD ; Основной регистр управления
BETA_DATA equ $3FFD ; Данные/статус
BETA_TRACK equ $5FFD ; Регистр дорожки
BETA_SECTOR equ $7FFD ; Регистр сектора
BETA_DMA equ $FFFD ; DMA регистр (младший/старший)
; Альтернативные адреса для разных клонов
BETA_ALT1 equ $7FFD ; Scorpion, Pentagon
BETA_ALT2 equ $DFFD ; Некоторые советские клоны
Принцип работы WD1793
Контроллер WD1793 содержит четыре основных регистра:- Командный регистр (запись) / Регистр статуса (чтение)
- Регистр дорожки
- Регистр сектора
- Регистр данных
Файловая система TR-DOS: полная анатомия
Структура дискеты
Код:
Дорожки: 0-79 (односторонняя) или 0-159 (двусторонняя)
Секторы: 1-16 на дорожке
Размер сектора: 256 байт
Общая емкость: 640 КБ (односторонняя) или 1.2 МБ (двусторонняя)
Зарезервированные дорожки: 0-2 (системная информация)
Сектор 0 дорожки 0: загрузочный сектор
Assembler Z80:
BOOT_SECTOR:
.signature db 0, 0, $22, $22, 0, 0 ; Сигнатура TR-DOS
.format_info db "80 track single side" ; Информация о формате
.unused ds 9, 0
.first_free_track db 16 ; Первая свободная дорожка
.disk_type db 0 ; 0 = 80 дорожек
.files_count db 128 ; Макс. файлов в каталоге
.free_sectors dw 0 ; Свободные секторы
.trdos_id db "TR-DOS" ; Идентификатор
.unused2 ds 240, 0
Каталог файлов (дорожки 0-2, сектора 1-8)
Каждая запись каталога — 16 байт:
Assembler Z80:
CATALOG_ENTRY:
.filename ds 8, ' ' ; Имя файла (дополняется пробелами)
.extension db 'C' ; Тип: C=CODE, B=BASIC, #=числовой массив
.start dw 0 ; Адрес загрузки
.length dw 0 ; Длина в байтах
.length_sectors db 0 ; Длина в секторах
.sector db 0 ; Начальный сектор
.track db 0 ; Начальная дорожка
.reserved db 0
Типы файлов в TR-DOS
Код:
'B' — Basic программа
'C' — Код (машинный код)
'D' — Массив чисел (Data array)
'#' — Числовой массив
'$' — Строковый массив
'A' — ASCII файл
Программирование контроллера на ассемблере Z80
Инициализация и базовые процедуры
Assembler Z80:
; Инициализация дисковода
INIT_DISK:
ld a, %00000001 ; Включить дисковод, мотор выкл
out (BETA_REG), a
; Ждем раскрутки мотора
ld b, 100
.spin_wait:
halt
djnz .spin_wait
ret
; Выбор дисковода
; A = номер дисковода (0-3)
SELECT_DRIVE:
and %00000011
or %00000100 ; Бит 2 = 1 (выбор дисковода)
out (BETA_REG), a
ret
Чтение сектора
Assembler Z80:
; Вход: B = дорожка (0-79), C = сектор (1-16)
; Выход: HL = буфер для данных (256 байт)
READ_SECTOR:
; Устанавливаем дорожку
ld a, b
out (BETA_TRACK), a
; Устанавливаем сектор
ld a, c
out (BETA_SECTOR), a
; Команда чтения сектора
ld a, %10001000 ; Чтение сектора + spin-up
out (BETA_DATA), a
; Ждем завершения
.wait:
in a, (BETA_DATA)
bit 0, a ; Бит busy
jr nz, .wait
; Проверяем ошибки
and %00011100 ; Ошибки: CRC, track0, index
jr nz, .error
; Читаем 256 байт в буфер
ld b, 0
ld c, BETA_DMA
.read_loop:
ini ; (HL) <- in (C), HL++, B--
jr nz, .read_loop
ret
.error:
scf ; Устанавливаем флаг переноса = ошибка
ret
Запись сектора
Assembler Z80:
; Вход: B = дорожка, C = сектор, HL = данные
WRITE_SECTOR:
; Устанавливаем дорожку и сектор
ld a, b
out (BETA_TRACK), a
ld a, c
out (BETA_SECTOR), a
; Команда записи сектора
ld a, %10101000 ; Запись сектора
out (BETA_DATA), a
; Записываем 256 байт
ld b, 0
ld c, BETA_DMA
.write_loop:
outi ; out (C), (HL)++, B--
jr nz, .write_loop
; Ждем завершения
.wait:
in a, (BETA_DATA)
bit 0, a
jr nz, .wait
; Проверка ошибок
and %00011100
ret z
scf ; Ошибка
ret
Поиск дорожки 0
Assembler Z80:
SEEK_TRACK0:
ld a, %00001000 ; Команда восстановления (seek track 0)
out (BETA_DATA), a
.wait:
in a, (BETA_DATA)
bit 0, a
jr nz, .wait
; Проверяем, что нашли track 0
and %00000100 ; Флаг track 0
jr z, .error
; Сбрасываем регистр дорожки
xor a
out (BETA_TRACK), a
ret
.error:
scf
ret
Работа с файловой системой TR-DOS
Чтение каталога диска
Assembler Z80:
; Загрузка каталога в буфер
LOAD_CATALOG:
ld hl, CATALOG_BUFFER
ld b, 0 ; Дорожка 0
ld c, 1 ; Сектор 1 (первый сектор каталога)
ld d, 8 ; 8 секторов каталога
.load_loop:
push bc
push de
push hl
call READ_SECTOR
jr c, .error
pop hl
ld de, 256
add hl, de ; Следующий буфер
pop de
pop bc
inc c ; Следующий сектор
dec d
jr nz, .load_loop
xor a ; Успех
ret
.error:
pop hl
pop de
pop bc
scf
ret
CATALOG_BUFFER:
ds 2048, 0 ; 8 секторов × 256 байт
Поиск файла в каталоге
Assembler Z80:
; Вход: HL = имя файла (8 символов)
; Выход: A = номер файла (0-127), HL = адрес записи
FIND_FILE:
ld de, CATALOG_BUFFER
ld b, 128 ; Максимум 128 файлов
ld c, 0 ; Счетчик файлов
.search_loop:
push hl
push bc
push de
; Сравниваем имя файла
ld b, 8
.compare:
ld a, (de)
cp (hl)
jr nz, .next_file
inc de
inc hl
djnz .compare
; Нашли файл
pop de ; DE = адрес записи
pop bc
pop hl
ld a, c ; A = номер файла
ret
.next_file:
pop de
ld hl, 16
add hl, de ; Следующая запись
ex de, hl
pop bc
pop hl
inc c
djnz .search_loop
; Файл не найден
scf
ret
Загрузка файла по имени
Assembler Z80:
; Вход: HL = имя файла, DE = адрес загрузки
LOAD_FILE:
push de
call FIND_FILE
pop de
ret c ; Файл не найден
; Читаем параметры файла
ld bc, 8
add hl, bc ; HL указывает на расширение
ld a, (hl) ; Тип файла
inc hl
ld c, (hl) ; start (младший байт)
inc hl
ld b, (hl) ; start (старший байт)
inc hl
push bc ; Сохраняем start адрес
ld c, (hl) ; length (младший)
inc hl
ld b, (hl) ; length (старший)
inc hl
push bc ; Сохраняем длину
ld c, (hl) ; length в секторах
inc hl
ld b, (hl) ; начальный сектор
inc hl
ld a, (hl) ; начальная дорожка
; Восстанавливаем параметры
pop hl ; HL = длина
pop bc ; BC = start адрес
; Если адрес загрузки = 0, используем start из каталога
ld a, d
or e
jr nz, .use_de
ex de, hl ; DE = длина
ld h, b
ld l, c ; HL = start
ex de, hl ; HL = длина, DE = start
.use_de:
; Теперь загружаем файл
; DE = адрес загрузки, A = дорожка, B = сектор, C = секторов
; Реализация чтения нескольких секторов
; ... (продолжение следует)
Форматирование дискеты
Низкоуровневое форматирование
Assembler Z80:
FORMAT_DISK:
; Проверяем, что диск защищен от записи
in a, (BETA_DATA)
bit 6, a ; Флаг write protect
jr nz, .write_protected
; Форматируем каждую дорожку
ld b, 80 ; 80 дорожек
ld c, 0 ; Текущая дорожка
.track_loop:
push bc
ld a, c
out (BETA_TRACK), a ; Устанавливаем дорожку
; Команда форматирования дорожки
ld a, %11111000 ; Форматирование + spin-up
out (BETA_DATA), a
; Формат секторов: C, H, R, N
; C=track, H=head, R=sector, N=sector size (256=1)
ld hl, SECTOR_TABLE
ld b, 16 ; 16 секторов
.format_loop:
ld a, (hl) ; Номер сектора
out (BETA_DMA), a
inc hl
djnz .format_loop
.wait:
in a, (BETA_DATA)
bit 0, a
jr nz, .wait
pop bc
inc c
djnz .track_loop
ret
.write_protected:
scf
ret
SECTOR_TABLE:
db 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
Оптимизированные процедуры для демо/игр
Быстрая загрузка без использования TR-DOS
Assembler Z80:
; Загрузка секторов напрямую (минуя файловую систему)
FAST_LOAD:
ld hl, LOAD_TABLE ; Таблица секторов для загрузки
.load_loop:
ld a, (hl) ; Флаг окончания (255)
cp 255
ret z
ld b, (hl) ; Дорожка
inc hl
ld c, (hl) ; Сектор
inc hl
ld e, (hl) ; Адрес буфера (младший)
inc hl
ld d, (hl) ; Адрес буфера (старший)
inc hl
push hl
ex de, hl
call READ_SECTOR
pop hl
jr nc, .load_loop
; Ошибка загрузки
ret
LOAD_TABLE:
db 0, 1, $00, $C0 ; Дорожка 0, сектор 1 -> C000h
db 0, 2, $00, $C1 ; Дорожка 0, сектор 2 -> C100h
; ...
db 255 ; Конец таблицы
Мультизагрузка с прерываниями
Assembler Z80:
; Фоновая загрузка во время отрисовки
BACKGROUND_LOAD:
di
ld (save_sp), sp
ld sp, $6000 ; Временный стек
; Сохраняем регистры
push af
push bc
push de
push hl
push ix
push iy
exx
push hl
push de
push bc
exx
; Загружаем один сектор
ld hl, (next_sector_ptr)
ld b, (hl) ; Дорожка
inc hl
ld c, (hl) ; Сектор
inc hl
ld e, (hl) ; Буфер младший
inc hl
ld d, (hl) ; Буфер старший
inc hl
ld (next_sector_ptr), hl
ex de, hl
call READ_SECTOR
; Восстанавливаем регистры
exx
pop bc
pop de
pop hl
exx
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ld sp, (save_sp)
ei
ret
save_sp: dw 0
next_sector_ptr: dw SECTOR_TABLE
Особенности разных клонов и интерфейсов
Pentagon 128K/512K
Scorpion ZS 256
ATM Turbo
Sinclair +3
Обслуживание и отладка
Диагностика дисковода
Assembler Z80:
CHECK_DRIVE:
; Проверка наличия диска
in a, (BETA_DATA)
bit 7, a ; Флаг ready
jr z, .no_disk
; Проверка защищенности от записи
bit 6, a
jr nz, .write_protected
; Проверка track 0
call SEEK_TRACK0
ret c
; Чтение первого сектора
ld b, 0
ld c, 1
ld hl, BUFFER
call READ_SECTOR
ret
.no_disk:
; Обработка отсутствия диска
ret
.write_protected:
; Обработка защиты от записи
ret
Калибровка дисковода
Assembler Z80:
CALIBRATE_DRIVE:
; Поиск track 0
call SEEK_TRACK0
ret c
; Перемещение на дорожку 40
ld a, 40
out (BETA_TRACK), a
ld a, %00011000 ; Seek команда
out (BETA_DATA), a
.wait1:
in a, (BETA_DATA)
bit 0, a
jr nz, .wait1
; Возврат на track 0
call SEEK_TRACK0
ret
Создание загрузчика игр
Интелектуальный загрузчик
Assembler Z80:
GAME_LOADER:
; Инициализация
call INIT_DISK
; Загрузка заголовка
ld hl, HEADER_BUFFER
ld b, 0
ld c, 1
call READ_SECTOR
; Проверка сигнатуры
ld hl, HEADER_BUFFER
ld de, SIGNATURE
ld b, 6
.check_sig:
ld a, (de)
cp (hl)
jr nz, .not_valid
inc hl
inc de
djnz .check_sig
; Загрузка основной программы
ld hl, (HEADER_BUFFER.load_addr)
ld bc, (HEADER_BUFFER.sector_count)
.load_main:
push bc
ld b, (ix) ; Дорожка из таблицы
ld c, (ix+1) ; Сектор из таблицы
call READ_SECTOR
pop bc
dec bc
ld a, b
or c
jr z, .load_complete
; Обновляем буфер
ld de, 256
add hl, de
inc ix
inc ix
jr .load_main
.load_complete:
jp (hl) ; Запуск игры
.not_valid:
; Обработка ошибки
ret
SIGNATURE: db "GAMELD"
Компрессия и декомпрессия на лету
Assembler Z80:
LOAD_COMPRESSED:
call READ_SECTOR ; Загружаем сжатые данные
ld hl, COMPRESSED_BUFFER
ld de, OUTPUT_BUFFER
.decompress:
ld a, (hl)
inc hl
cp $FF ; Маркер конца
ret z
bit 7, a
jr z, .raw_byte
; RLE распаковка
and $7F ; Длина
ld b, a
ld a, (hl) ; Повторяемый байт
inc hl
.fill:
ld (de), a
inc de
djnz .fill
jr .decompress
.raw_byte:
ld (de), a ; Обычный байт
inc de
jr .decompress
Современные эмуляторы и инструменты
Работа с образами дисков
Форматы образов:- .TRD — точный образ TR-DOS дискеты
- .SCL — сжатый каталог файлов
- .FDI — образ с информацией о формате
- .UDI — универсальный образ
Инструменты разработки
- TRD Manager — создание и редактирование образов
- Disk Explorer — просмотр содержимого дискет
- EmuZWin — эмулятор с поддержкой дисководов
- FUSE — кроссплатформенный эмулятор
- bintap — конвертер между форматами
Заключение: наследие дисковых контроллеров Spectrum
Контроллеры дисководов для ZX Spectrum, особенно Beta Disk с TR-DOS, стали неотъемлемой частью культуры Spectrum. Они позволили:- Ускорить загрузку в 100+ раз (с 5 минут до 3 секунд)
- Создать сложные многофайловые игры
- Реализовать профессиональные приложения
- Обеспечить удобное хранение данных
- Эффективность через простоту — WD1793 имел всего 4 регистра
- Важность стандартизации — TR-DOS стал общим языком для клонов
- Оптимизация под железо — прямое программирование контроллера
- Обратная совместимость — большинство решений сохраняли работу с кассетами
Сегодня эти технологии живут в эмуляторах, ретро-сборках и сердцах фанатов, напоминая о времени, когда каждый байт имел значение, а программирование было настоящим искусством низкоуровневого взаимодействия с железом.