В связи с тем, что существующие решения достаточно сложные и дорогостоящие возникла идея сделать максимально дешевый, простой и при этом достаточно функциональный девайс на Atmega8.
После обсуждений решено, что проект будет реализовываться на Arduino!
Проект уже работает на Arduino с SD картой! Поддерживается только чтение, записи нет!
Внимание, при использовании Arduino Pro Mini пины RX/TX расположены наоборот!
Исходники проекта на GitHub: https://github.com/EvgeniyRU/ZX_FDD_Emulator Схема подключения находится там же.
В GitHub можно скачать любую предыдущую версию прошивки выбрав в списке коммитов https://github.com/EvgeniyRU/ZX_FDD_...commits/master нужный коммит, затем Browse Files (справа), затем Clone or Download и после Download ZIP
В последних версиях работа ведется с SD/SDHC/MMC картой, файловая система FAT32, поддерживаются карты от 512Мб до 32Гб, больше не проверялись. Данные берутся из TRD образов, дисплей 1602.
За основу взята следующая спецификация флопа FD1035_Product_Description_Jul84.pdf.
Еще вот этот документ принимается во внимание http://www.mcamafia.de/pdf/ps2_fdd_trm_s42g2194_00.pdf
Насколько я понимаю, работа с устройством происходит по сигналам
1. DRIVE SELECT выбирает нужное устройство из 4-ех DS0-DS3, на нужном устанавливается уровень LOW (INPUT)
2. STEP двигает голову на дорожку вперед/назад в зависимости от сигнала DIRECTION (INPUT)
3. DIRECTION - направление движения головок HIGH - к центру, LOW - к краю (track 0).
4. SIDE SELECT выбирает сторону HIGH - Head 0, LOW - Head 1 (INPUT)
5. WRITE GATE определяет ЧТЕНИЕ[HIGH]/ЗАПИСЬ[LOW] (INPUT)
6. WRITE DATA данные которые записываются на дискету (INPUT)
7. MOTOR ON - запуск шпинделя LOW запуск, HIGH останов (INPUT)
8. TRK00 - генерируется, если головка на нулевой дорожке, LOW означает, что мы на нулевой дорожке (OUTPUT)
9. READY - насколько я понял, для ZX не актуален (OUTPUT) ***
10. READ DATA - данные считываемые с дискеты и отправляемые в BDI (OUTPUT) формат MFM
11. WRITE PROTECT - генерируется для защиты от записи (LOW), на этапе разработки он всегда будет генерироваться, пока не заработает чтение нормально, потом уже перейду к разработке записи, если это будет представляться возможным. (OUTPUT)
Принцип работы устройства (насколько я пока что понимаю)
1. Читаем нулевой цилиндр, вычисляем CRC для секторов нижней и верхней дорожки, выставляем TRK00
(можно вычислить CRC для всех дорожек при монтировании)
2. Организуется цикл c выходом на повтор, если нужный нам DS в положении HIGH
Как только он стал LOW и MOTOR ON - LOW начинаем обрабатывать
3. Обработчик
a) Вешаем пин STEP на прерывание.
b) Методично (циклично) шлем данные дорожки посекторно в READ DATA в формате MFM, пульсируя INDEX в начале отправки дорожки
b2) После отправки сектора проверяем DS и MOTOR ON, если не наши значения, отключаем прерывания, переходим на пункт 2
------------------------
Функция STEP (в обработчике прерывания)
1. Проверяем DIRECTION, HIGH - увеличиваем дорожку (прибавляем к смещению 8192 байт), LOW - проверяем, если дорожка больше нуля - уменьшаем (вычитаем 8192 байт), если дорожка стала = 0, ставим TRACK00 в LOW.
2. Если трек изменился ставим флаг об изменении трека.
3. Выходим из прерывания.
Вроде с виду всё просто, но есть много вопросов.
1. Где посмотреть формат передаваемых данных (READ DATA, WRITE DATA) Уже разобрался
2. Как происходят операции с секторами, пишется/читается вся дорожка целиком? С этим тоже разобрался
3. Почему вход SIDE пульсирует? (тоже разобрался, в процессе чтения сторона может измениться в любой момент)
То что уже нашел буду отмечать красным, то что уже неактуально зеленым.
Информацию по сигналам возьму отсюда http://zx-pk.ru/showthread.php?t=126...l=1#post825463
Нашел еще интересную информацию:
http://www.avrfreaks.net/comment/328274#comment-328274
PS: по форматам дорожек нашел инфу, есть в экселевском файле по ссылке про сигналы, но в книжке Ларченко и Родионова приводится немного другая инфа http://zxpress.ru/book_articles.php?id=1868 http://zxpress.ru/article.php?id=8992 Кому верить? (оказалось, формат может немного отличаться)
PS2: Так в целом понятно по формату дороги, если смотреть по экселю, там для адресного поля CRC можно заранее табличку сделать в коде прошивки, данные подсовываются в поле данных, с этим тоже не сложно, 256 байт можно в памяти хранить и считывать следующие перед генерацией INDEX, вот CRC для поля данных налету генерировать это уже сложнее, хотя в процессе отправки думаю можно успеть, а можно и при открытии образа сгенерировать, но налету кажется это сделать проще.
Налету оказалось достаточно просто генерировать CRC по таблице размещенной во Flash памяти
PS3: пожалуй начну с того, что буду генерировать пустой образ дискеты, т.е. чистой отформатированной, если диск доктор начнет читать нормально нули в секторах, то буду двигаться дальше. Это было сделано достаточно быстро
Вот, кстати, откуда ноги растут
Окончательный вариант формата дорожки, в конце дорожки 598 байт это крайний случай, реально конечно будет меньше
ВНИМАНИЕ!!! В описании дорожки данные в ячейках A1 и C2 являются командами контроллера дисковода! Т.е. на их месте в закодированном виде должны быть значения 0x4489 и 0x5224 соответственно
Генерация CRC16 для Адресного поля и Поля данных
Используется CRC-CCITT нач. значение 0xFFFF, полином 0x1021. Кодируются данные вместе с A1 и маркером, начиная с первого значения 0xA1, т.е. используется именно A1!!!
Снял диаграмму сигналов на лог анализаторе, вот уменьшенная версия, чтобы примерно понять, что и как происходит, на картинке второй STEP это SIDE.
Выходит, по приходу сигнала MOTOR ON дисковод начинает слать данные в READ DATA без перерыва и WG и WD стоят неактивные в режиме чтения, так что всё еще проще оказалось.
Расписываю основные времянки, снимал на частоте 1МГц:
INDEX - LOW - 3.52ms, между индексами HIGH примерно 196ms, погрешность 0.1%
MOTOR активен LOW пока требуется чтение
WR GATE, WR DATA всегда не активны HIGH в режиме чтения
TRK00 всегда выставлено LOW после прихода MOTOR ON
STEP период четко 3мс, пульс 1-2мкс
READ DATA пульсирует LOW на время 1мкс, вот как выглядит
Интервал окна составляет 4, 6 и 8 мкс
ВАЖНО! По поводу кодирования в MFM отличная инфа здесь http://firmware.altervista.org/Data%...20Decoding.htm и тут http://info-coach.fr/atari/hardware/...le_of_CRC_Code
Вот из этой картинки становится понятно, что и как кодируется
Схема кодирования такая
Например, есть байт 0xC6 - 1100 0110
Если бит данных равен 1, то он заменяется на 01
Если бит данных равен 0, предыдущий бит равен 0, то он заменяется на 10
Если бит данных равен 0, предыдущий бит равен 1, то он заменяется на 00
Кодированными данными будет значение |01|01|00|10|10|01|01|00|
Разбиваем это по биту 1, получаем 10, 100, 10, 100, 10, 100
На выходе получаем сигналы такой последовательности:
4мкс(10), 6мкс(100), 4мкс(10), 6мкс(100), 4мкс(10), 6мкс(100)
Остается только скорректировать первый бит последовательности в потоке данных, если старший бит равен 0, то проверить младший предыдущего, и на основе этого скорректировать.
Получается такая схема, если встретилась единица в потоке данных, то опускаем уровень в LOW на 1мкс, затем поднимаем и ждем 1мкс, если встретился ноль, то просто ждем 2мкс, всё оооочень просто
Кодировать данные проще всего по таблице ОБНОВЛЕНА 28.03.2016
0xAAAA, 0xAAA9, 0xAAA4, 0xAAA5, 0xAA92, 0xAA91, 0xAA94, 0xAA95,
0xAA4A, 0xAA49, 0xAA44, 0xAA45, 0xAA52, 0xAA51, 0xAA54, 0xAA55,
0xA92A, 0xA929, 0xA924, 0xA925, 0xA912, 0xA911, 0xA914, 0xA915,
0xA94A, 0xA949, 0xA944, 0xA945, 0xA952, 0xA951, 0xA954, 0xA955,
0xA4AA, 0xA4A9, 0xA4A4, 0xA4A5, 0xA492, 0xA491, 0xA494, 0xA495,
0xA44A, 0xA449, 0xA444, 0xA445, 0xA452, 0xA451, 0xA454, 0xA455,
0xA52A, 0xA529, 0xA524, 0xA525, 0xA512, 0xA511, 0xA514, 0xA515,
0xA54A, 0xA549, 0xA544, 0xA545, 0xA552, 0xA551, 0xA554, 0xA555,
0x92AA, 0x92A9, 0x92A4, 0x92A5, 0x9292, 0x9291, 0x9294, 0x9295,
0x924A, 0x9249, 0x9244, 0x9245, 0x9252, 0x9251, 0x9254, 0x9255,
0x912A, 0x9129, 0x9124, 0x9125, 0x9112, 0x9111, 0x9114, 0x9115,
0x914A, 0x9149, 0x9144, 0x9145, 0x9152, 0x9151, 0x9154, 0x9155,
0x94AA, 0x94A9, 0x94A4, 0x94A5, 0x9492, 0x9491, 0x9494, 0x9495,
0x944A, 0x9449, 0x9444, 0x9445, 0x9452, 0x9451, 0x9454, 0x9455,
0x952A, 0x9529, 0x9524, 0x9525, 0x9512, 0x9511, 0x9514, 0x9515,
0x954A, 0x9549, 0x9544, 0x9545, 0x9552, 0x9551, 0x9554, 0x9555,
0x4AAA, 0x4AA9, 0x4AA4, 0x4AA5, 0x4A92, 0x4A91, 0x4A94, 0x4A95,
0x4A4A, 0x4A49, 0x4A44, 0x4A45, 0x4A52, 0x4A51, 0x4A54, 0x4A55,
0x492A, 0x4929, 0x4924, 0x4925, 0x4912, 0x4911, 0x4914, 0x4915,
0x494A, 0x4949, 0x4944, 0x4945, 0x4952, 0x4951, 0x4954, 0x4955,
0x44AA, 0x44A9, 0x44A4, 0x44A5, 0x4492, 0x4491, 0x4494, 0x4495,
0x444A, 0x4449, 0x4444, 0x4445, 0x4452, 0x4451, 0x4454, 0x4455,
0x452A, 0x4529, 0x4524, 0x4525, 0x4512, 0x4511, 0x4514, 0x4515,
0x454A, 0x4549, 0x4544, 0x4545, 0x4552, 0x4551, 0x4554, 0x4555,
0x52AA, 0x52A9, 0x52A4, 0x52A5, 0x5292, 0x5291, 0x5294, 0x5295,
0x524A, 0x5249, 0x5244, 0x5245, 0x5252, 0x5251, 0x5254, 0x5255,
0x512A, 0x5129, 0x5124, 0x5125, 0x5112, 0x5111, 0x5114, 0x5115,
0x514A, 0x5149, 0x5144, 0x5145, 0x5152, 0x5151, 0x5154, 0x5155,
0x54AA, 0x54A9, 0x54A4, 0x54A5, 0x5492, 0x5491, 0x5494, 0x5495,
0x544A, 0x5449, 0x5444, 0x5445, 0x5452, 0x5451, 0x5454, 0x5455,
0x552A, 0x5529, 0x5524, 0x5525, 0x5512, 0x5511, 0x5514, 0x5515,
0x554A, 0x5549, 0x5544, 0x5545, 0x5552, 0x5551, 0x5554, 0x5555,В таблице предполагается, что последний бит предыдущего байта данных был равен 0! Поэтому, если это не так, то нужно корректировать старший бит, если старший бит текущего байта данных равен нулю!0x00:0xAAAA, 0x01:0xAAA9, 0x02:0xAAA4, 0x03:0xAAA5, 0x04:0xAA92, 0x05:0xAA91, 0x06:0xAA94, 0x07:0xAA95,
0x08:0xAA4A, 0x09:0xAA49, 0x0A:0xAA44, 0x0B:0xAA45, 0x0C:0xAA52, 0x0D:0xAA51, 0x0E:0xAA54, 0x0F:0xAA55,
0x10:0xA92A, 0x11:0xA929, 0x12:0xA924, 0x13:0xA925, 0x14:0xA912, 0x15:0xA911, 0x16:0xA914, 0x17:0xA915,
0x18:0xA94A, 0x19:0xA949, 0x1A:0xA944, 0x1B:0xA945, 0x1C:0xA952, 0x1D:0xA951, 0x1E:0xA954, 0x1F:0xA955,
0x20:0xA4AA, 0x21:0xA4A9, 0x22:0xA4A4, 0x23:0xA4A5, 0x24:0xA492, 0x25:0xA491, 0x26:0xA494, 0x27:0xA495,
0x28:0xA44A, 0x29:0xA449, 0x2A:0xA444, 0x2B:0xA445, 0x2C:0xA452, 0x2D:0xA451, 0x2E:0xA454, 0x2F:0xA455,
0x30:0xA52A, 0x31:0xA529, 0x32:0xA524, 0x33:0xA525, 0x34:0xA512, 0x35:0xA511, 0x36:0xA514, 0x37:0xA515,
0x38:0xA54A, 0x39:0xA549, 0x3A:0xA544, 0x3B:0xA545, 0x3C:0xA552, 0x3D:0xA551, 0x3E:0xA554, 0x3F:0xA555,
0x40:0x92AA, 0x41:0x92A9, 0x42:0x92A4, 0x43:0x92A5, 0x44:0x9292, 0x45:0x9291, 0x46:0x9294, 0x47:0x9295,
0x48:0x924A, 0x49:0x9249, 0x4A:0x9244, 0x4B:0x9245, 0x4C:0x9252, 0x4D:0x9251, 0x4E:0x9254, 0x4F:0x9255,
0x50:0x912A, 0x51:0x9129, 0x52:0x9124, 0x53:0x9125, 0x54:0x9112, 0x55:0x9111, 0x56:0x9114, 0x57:0x9115,
0x58:0x914A, 0x59:0x9149, 0x5A:0x9144, 0x5B:0x9145, 0x5C:0x9152, 0x5D:0x9151, 0x5E:0x9154, 0x5F:0x9155,
0x60:0x94AA, 0x61:0x94A9, 0x62:0x94A4, 0x63:0x94A5, 0x64:0x9492, 0x65:0x9491, 0x66:0x9494, 0x67:0x9495,
0x68:0x944A, 0x69:0x9449, 0x6A:0x9444, 0x6B:0x9445, 0x6C:0x9452, 0x6D:0x9451, 0x6E:0x9454, 0x6F:0x9455,
0x70:0x952A, 0x71:0x9529, 0x72:0x9524, 0x73:0x9525, 0x74:0x9512, 0x75:0x9511, 0x76:0x9514, 0x77:0x9515,
0x78:0x954A, 0x79:0x9549, 0x7A:0x9544, 0x7B:0x9545, 0x7C:0x9552, 0x7D:0x9551, 0x7E:0x9554, 0x7F:0x9555,
0x80:0x4AAA, 0x81:0x4AA9, 0x82:0x4AA4, 0x83:0x4AA5, 0x84:0x4A92, 0x85:0x4A91, 0x86:0x4A94, 0x87:0x4A95,
0x88:0x4A4A, 0x89:0x4A49, 0x8A:0x4A44, 0x8B:0x4A45, 0x8C:0x4A52, 0x8D:0x4A51, 0x8E:0x4A54, 0x8F:0x4A55,
0x90:0x492A, 0x91:0x4929, 0x92:0x4924, 0x93:0x4925, 0x94:0x4912, 0x95:0x4911, 0x96:0x4914, 0x97:0x4915,
0x98:0x494A, 0x99:0x4949, 0x9A:0x4944, 0x9B:0x4945, 0x9C:0x4952, 0x9D:0x4951, 0x9E:0x4954, 0x9F:0x4955,
0xA0:0x44AA, 0xA1:0x44A9, 0xA2:0x44A4, 0xA3:0x44A5, 0xA4:0x4492, 0xA5:0x4491, 0xA6:0x4494, 0xA7:0x4495,
0xA8:0x444A, 0xA9:0x4449, 0xAA:0x4444, 0xAB:0x4445, 0xAC:0x4452, 0xAD:0x4451, 0xAE:0x4454, 0xAF:0x4455,
0xB0:0x452A, 0xB1:0x4529, 0xB2:0x4524, 0xB3:0x4525, 0xB4:0x4512, 0xB5:0x4511, 0xB6:0x4514, 0xB7:0x4515,
0xB8:0x454A, 0xB9:0x4549, 0xBA:0x4544, 0xBB:0x4545, 0xBC:0x4552, 0xBD:0x4551, 0xBE:0x4554, 0xBF:0x4555,
0xC0:0x52AA, 0xC1:0x52A9, 0xC2:0x52A4, 0xC3:0x52A5, 0xC4:0x5292, 0xC5:0x5291, 0xC6:0x5294, 0xC7:0x5295,
0xC8:0x524A, 0xC9:0x5249, 0xCA:0x5244, 0xCB:0x5245, 0xCC:0x5252, 0xCD:0x5251, 0xCE:0x5254, 0xCF:0x5255,
0xD0:0x512A, 0xD1:0x5129, 0xD2:0x5124, 0xD3:0x5125, 0xD4:0x5112, 0xD5:0x5111, 0xD6:0x5114, 0xD7:0x5115,
0xD8:0x514A, 0xD9:0x5149, 0xDA:0x5144, 0xDB:0x5145, 0xDC:0x5152, 0xDD:0x5151, 0xDE:0x5154, 0xDF:0x5155,
0xE0:0x54AA, 0xE1:0x54A9, 0xE2:0x54A4, 0xE3:0x54A5, 0xE4:0x5492, 0xE5:0x5491, 0xE6:0x5494, 0xE7:0x5495,
0xE8:0x544A, 0xE9:0x5449, 0xEA:0x5444, 0xEB:0x5445, 0xEC:0x5452, 0xED:0x5451, 0xEE:0x5454, 0xEF:0x5455,
0xF0:0x552A, 0xF1:0x5529, 0xF2:0x5524, 0xF3:0x5525, 0xF4:0x5512, 0xF5:0x5511, 0xF6:0x5514, 0xF7:0x5515,
0xF8:0x554A, 0xF9:0x5549, 0xFA:0x5544, 0xFB:0x5545, 0xFC:0x5552, 0xFD:0x5551, 0xFE:0x5554, 0xFF:0x5555
ПОСЛЕДНИЕ ИССЛЕДОВАНИЯ ПОКАЗАЛИ, ЧТО ДАННЫЙ ШАГ МОЖНО ОПУСТИТЬ!!! Т.Е. бит можно не исправлять!!!
Самые последние исследования показали, что достаточно менять 1 на 01, а 0 на 10 (для инвертированного сигнала наоборот) и контроллер такой сигнал нормально хавает через Master SPI
ВНИМАНИЕ! Вышесказанное справедливо только для ВГ93!!! Если у вас другой контроллер (импортный), то, скорее всего, придется делать полное MFM кодирование, что, в принципе, делается в последних версиях прошивки.
Сигнал генерируется с использованием USART в режиме MasterSPI (поддерживается контроллерами Atmega48/88/168/328, может еще какими-то но точно не Atmega8,128...) Затем сигнал инвертируется (проще всего программно) на выходе получается код, совместимый с MFM.
Пример
Спасибо за комментарии.Код:uint8_t MFM_tab[32] = { 0xAA,0xA9,0xA4,0xA5,0x92,0x91,0x94,0x95,0x4A,0x49,0x44,0x45,0x52,0x51,0x54,0x55, 0x2A,0x29,0x24,0x25,0x12,0x11,0x14,0x15,0x4A,0x49,0x44,0x45,0x52,0x51,0x54,0x55, }; first_mfm_byte = sector_byte; // current data byte first_mfm_byte >>= 4; first_mfm_byte = MFM_tab[first_mfm_byte]; // get first MFM byte from table if((prev_byte & 1) && !(sector_byte & 0x80)) first_mfm_byte &= 0x7F; prev_byte = sector_byte; second_mfm_byte = sector_byte & 0x1f; second_mfm_byte = MFM_tab[second_mfm_byte]; // get second MFM byte from table