亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

Arduino Amiga 软盘阅读器 (V1)

组件和用品

电阻 1k ohm
× 1
面包板(通用)
× 1
跳线(通用)
× 1
SparkFun 面包板电源 5V/3.3V
× 1
Arduino UNO
或者 Pro Mini
× 1

应用和在线服务

Arduino IDE

关于这个项目

  • 我的目标: 创建一种简单、廉价且开源的方式,从 Windows 10 和其他操作系统中的 Amiga DD 软盘中恢复数据。
  • 我的解决方案: 一个 Arduino + 一个 Windows 应用程序
  • 为什么: 为将来保留这些磁盘中的数据。此外,由于 Amiga 磁盘的写入方式,普通 PC 无法读取它们。

项目网址:http://amiga.robsmithdev.co.uk/

这是项目的V1。 V2 包含改进的读写!

Amiga 体验

我的职业生涯归功于 Amiga,特别是我父母在 10 岁时为我购买的圣诞节 A500+。起初我玩游戏,但过了一段时间我开始好奇它还能做什么。我玩过 Deluxe Paint III 并了解了 Workbench。

Amiga 500 Plus:

每个月我都会购买流行的 Amiga Format 杂志。一个月有一份 AMOS 的免费副本。我输入了 Amiga 格式 在 AMOS 中编写游戏 后来 AMOS Professional 被放到了封面上,并且是 In The Pipe Line 的 12 个(我认为)获胜者之一 .不过,你真的不得不追逐他们以获得奖品!

AMOS - 造物主:

背景

继续前进,我将它用作我的 GCSE 和 A-Level 项目的一部分(感谢 Highspeed Pascal,它与 PC 上的 Turbo Pascal 兼容)

不管怎样,那是很久以前的事了,我有几盒磁盘,还有一个不能用的 A500+,所以我想把这些磁盘备份到我的电脑上,既保存又怀旧。

Amiga Forever 网站有一个很好的选项列表,包括硬件和在 PC 中滥用两个软盘驱动器 - 遗憾的是,这些都不是现代硬件的选项,而且 KryoFlux/Catweasel 控制器太贵了。我真的很惊讶大部分都是闭源的。

大量涉足电子产品并使用过 Atmel 设备 (AT89C4051 ) 在大学期间,我决定看看 Arduino(归功于 GreatScott 的灵感表明开始是多么容易)我想知道这是否可能。

所以我在 Google 上搜索 Arduino 软盘驱动器阅读 代码,并在跳过所有滥用的项目之后 播放音乐的动力,我真的没有找到任何解决方案。我在几个小组中发现了一些讨论,表明这是不可能的。我确实找到了一个基于FPGA的项目,读起来很有趣,但不是我想要的方向,所以唯一的选择是自己构建一个解决方案。

研究

当我开始这个项目时,我不知道软盘驱动器是如何工作的,更不用说数据是如何编码到它们的了。以下网站对我了解发生的事情及其工作方式非常宝贵:

  • techtravels.org(和本页)
  • Lau​​rent Clévy 的 .ADF(Amiga 磁盘文件)格式常见问题
  • 永远的阿米加
  • 维基百科 - Amiga 磁盘文件
  • 英语 Amiga Board
  • QEEWiki - ATmega168/328 上的计数器
  • 软驱引出线
  • 软盘格式列表

假设

根据研究,我现在从理论上知道数据是如何写入磁盘的,以及磁盘是如何旋转的。

我开始计算一些数字。基于双密度磁盘的旋转速度 (300rpm) 和数据存储方式(80 磁道,每磁道 11 个扇区和每扇区 512 字节,使用 MFM 编码),我需要能够准确读取数据以 500Khz 采样数据;当您考虑 Arduino 仅以 16Mhz 运行时,这是相当快的。

在接下来的尝试中,我只谈论 Arduino 方面。跳转到解码。

尝试 1:

首先,我需要收集软驱的硬件和接口。我在工作时从旧电脑上拿的软驱,同时抓住了它的IDE电缆。

下面是一张解放的照片 旧电脑的软盘驱动器:

研究驱动器的引脚排列后,我意识到我只需要一些电线,在查看驱动器后,我意识到它也没有使用 12v 输入。

通过选择驱动器并启用电机来实现驱动器旋转。移动头部很简单。您设置 /DIR 引脚高或低,然后脉冲/STEP 别针。您可以通过监视 /TRK00 来判断磁头是否到达了第 0 个磁道(第一个磁道) 别针。

我对 /INDEX 很好奇 别针。每次旋转都会脉冲一次。由于 Amiga 不使用它来查找曲目的起点,因此我不需要它并且可以忽略它。在此之后,只需选择要读取磁盘的哪一侧 (/SIDE1 ) 并连接 /RDATA .

对于高数据速率要求,我的第一个想法是找到一种方法,通过尝试降低对此速率的要求来减少这个问题。

计划是使用两个 8 位移位寄存器 (SN74HC594N ) 将所需的采样频率降低 8 倍。我使用的是 Ebay 所谓的 Pro Mini atmega328 Board 5V 16M Arduino Compatible Nano (所以我不知道那是什么官方的,但这在 Uno 上确实有效!)缓冲这个 parallel 数据并使用它的串行/USART 接口将其发送到 PC。我知道这需要运行速度超过 500Kbaud(还涉及所有串行开销)。

sn74hc594.pdf

抛弃标准的 Arduino 串行库后,我很高兴地发现我可以在 Arduino 上以 uptp 2M 波特率配置 USART,并使用其中一个 F2DI 分线板(eBay 称其为 FTDI FT232RL 的基本分线板) Arduino的USB转串行IC - 见下文)我可以很高兴地以这种速率(62.5Khz)发送和接收数据,但我需要准确地做到这一点。

Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf

完美贴合Arduino板上接口的FTDI分线板:

首先,我使用 Arduino 来设置 8 位移位寄存器中只有一个时钟为高的 8 位。另一个直接从软盘驱动器接收馈送(从而提供串行到并行的转换)。

以下是我当时在其上构建的面包板的疯狂图片:

我使用其中一个 Arduinos 计时器在其输出引脚之一上生成 500Khz 信号,并且当硬件管理此信号时,它非常准确! - 好吧,无论如何,我的万用表测得的准确度为 500khz。

代码有效,我以 62.5khz 的频率记录了完整的 8 位数据,几乎没有使用 Arduino CPU。然而,我没有收到任何有意义的东西。在这一点上,我意识到我需要仔细查看从软盘驱动器中输出的实际数据。所以我从 eBay 购买了一个便宜的旧示波器(Gould OS300 20Mhz Oscilloscope)来看看发生了什么。

在等待示波器到达的同时,我决定尝试别的东西。

用于从移位寄存器读取数据的一段代码:

void readTrackData() { byte op; for (int a=0; a<5632; a++) { // 我们将等待“字节”开始标记 while (digitalRead(PIN_BYTE_READ_SIGNAL)==LOW) {}; // 读取字节 op=0; if (digitalRead(DATA_LOWER_NIBBLE_PB0)==HIGH) op|=1; if (digitalRead(DATA_LOWER_NIBBLE_PB1)==HIGH) op|=2; if (digitalRead(DATA_LOWER_NIBBLE_PB2)==HIGH) op|=4; if (digitalRead(DATA_LOWER_NIBBLE_PB3)==HIGH) op|=8; if (digitalRead(DATA_UPPER_NIBBLE_A0)==HIGH) op|=16; if (digitalRead(DATA_UPPER_NIBBLE_A1)==HIGH) op|=32; if (digitalRead(DATA_UPPER_NIBBLE_A2)==HIGH) op|=64; if (digitalRead(DATA_UPPER_NIBBLE_A3)==HIGH) op|=128; writeByteToUART(op); // 等待高再次下降 while (digitalRead(PIN_BYTE_READ_SIGNAL)==HIGH) {}; }} 

尝试 2:

我决定使用移位寄存器,虽然一个好主意可能没有帮助。我能够轻松地一次性读取 8 位,但我突然想到,我无法确定所有位的计时是否正确。阅读文档表明数据更多是短脉冲而不是高低。

我移除了移位寄存器,想知道如果我尝试使用先前设置的 500Khz 信号在中断 (ISR) 中检查来自驱动器的脉冲会发生什么。我重新配置了 Arduino 以生成 ISR,在我通过了 Arduino 库的问题(使用我想要的 ISR)之后,我转向了 Timer 2。

我写了一个简短的 ISR,它将全局单字节左移一位,然后如果连接到软盘驱动器数据线的引脚为 LOW (脉冲很低)我会或一个 1 到它上面。每执行 8 次,我就会将完成的字节写入 USART。

这并不像预期的那样! Arduino 开始表现得非常不稳定和奇怪。我很快意识到 ISR 的执行时间比调用它的时间长。我可以根据 Arduino 的速度每 2 微秒接收一个脉冲,并假设每条 C 指令 转换为1个时钟机器代码周期, 我意识到我最多可以有 32 个指令。遗憾的是,大多数指令不止一条,在谷歌搜索之后我意识到启动 ISR 的开销无论如何都是巨大的;更不用说digitalRead函数很慢了。

我放弃了digitalRead 支持直接访问端口引脚的功能!这仍然没有帮助,也不够快。不准备放弃,我搁置了这种方法并决定继续尝试其他方法。

这时我购买的示波器到了,它工作了!一个可能比我还老的老旧示波器!但仍然完美地完成了这项工作。 (如果您不知道什么是示波器,请查看 EEVblog #926 - 示波器简介,如果您喜欢电子产品,那么我建议您多看一些并浏览 EEVBlog 网站。

我新买的老旧示波器(Gould OS300 20Mhz):

将 500Khz 信号连接到一个通道并将软盘驱动器的输出连接到另一个通道后,很明显有些不对劲。 500Khz 的信号是一个完美的方波,用它作为触发器,软盘数据到处都是。我能看到脉冲,但更模糊。同样如果我从软驱信号触发,500Khz信号方波信号到处都是,不同步。

示波器上两个通道触发的轨迹照片。你看不出来,但在频道上不是 被触发的是成千上万条微弱的幽灵线:

我可以单独测量来自 500Khz 的两个信号的脉冲,这没有意义,好像它们都以相同的速度运行但不会触发,因此您可以正确地看到两个信号,那么一定有问题。

在玩了很多触发级别之后,我设法弄清楚发生了什么。我的信号是完美的 500Khz,但是查看软驱信号,它们的间隔是正确的,但并非总是如此。脉冲组之间存在误差漂移,数据中也存在使信号完全不同步的间隙。

记得之前的研究,驱动器应该以 300 rpm 的速度旋转,但实际上可能不是 300 rpm,而且写入数据的驱动器也可能不是 300 rpm。然后是扇区之间的间距和扇区间隙。显然存在同步问题,在读取开始时将 500Khz 信号同步到软盘驱动器是行不通的。

我还发现来自软盘驱动器的脉冲非常短,虽然你可以通过改变上拉电阻来修改它,如果时间不完全正确,那么 Arduino 可能会错过一个脉冲。

当我在大学(莱斯特大学)时,我学习了一个名为嵌入式系统的模块。我们研究了 Atmel 8051 微控制器。其中一个项目涉及对来自模拟气象站(旋转编码器)的脉冲进行计数。当时我定期对引脚进行采样,但这不是很准确。

模块讲师,Prof Pont 建议我应该使用硬件计数器 设备的功能(我什至不知道它有一次。)

我检查了 ATMega328 的数据表,确定三个定时器中的每一个都可以配置为对从外部输入触发的脉冲进行计数。这意味着速度不再是问题。我真正需要知道的是脉冲是否发生在 2µSec 的时间窗口内。

尝试 3:

我调整了 Arduino 草图以在检测到第一个脉冲时重置 500khz 计时器,并且每次 500khz 计时器溢出我检查计数器值以查看是否检测到脉冲。然后我执行了相同的位移序列,每 8 位向 USART 写入一个字节。

数据进来了,我开始在 PC 上分析它。在数据中,我开始看到看似有效的数据。会出现奇数同步字或 0xAAAA 序列组,但没有任何可靠的信息。我知道我正在做一些事情,但仍然缺少一些东西。

尝试 4:

我意识到在读取数据时,来自驱动器的数据可能与我的 500khz 信号不同步/相位。我通过每次开始阅读时只读取 20 个字节来确认这一点。

阅读有关如何处理此同步问题的信息时,我偶然发现了锁相环或 PLL 一词。简单来说,对于我们正在做的事情,锁相环会动态调整时钟频率(500khz)以补偿信号中的频率漂移和变化。

计时器的分辨率不够高,无法以足够小的数量(例如 444khz、470khz、500khz、533khz、571khz 等)来改变它,而要正确执行此操作,我可能需要代码运行得更快。

Arduino 计时器的工作方式是计数到预定义的数字(在本例中为 16 表示 500khz ) 然后他们设置一个溢出寄存器并重新从 0 开始。实际的计数器值可以随时读取和写入。

我调整草图以循环等待,直到定时器溢出,当它溢出时,我像以前一样检查脉冲。这次的不同在于什么时候 在循环内检测到脉冲,我将定时器计数器值重置为预定义的阶段 位置,有效地将定时器与每个脉冲重新同步。

我选择了我写给定时器计数器的值,这样它会从检测脉冲(中途)开始溢出 1µSec,这样下次定时器溢出时,脉冲就会相隔 2µSec。

这有效!我现在正在从磁盘读取几乎完美的数据。我仍然收到很多令人讨厌的校验和错误。我通过不断重新读取驱动器上的同一磁道来解决其中的大部分问题,直到所有 11 个扇区都具有有效的标头和数据校验和。

我在这一点很好奇,所以我再次将它们全部连接回示波器,看看现在发生了什么,正如我猜测的那样,我现在可以看到两条轨迹,因为它们都保持同步:

我很想更清楚地看到这一点,如果有人想捐赠我一个可爱的顶级数字示波器(例如其中一个 Keysight 示波器!),我将不胜感激!

尝试 5:

我想知道我是否可以改进这一点。查看代码,特别是内部读取循环(见下文)我有一个 while 循环等待溢出,然后是一个内部 if 寻找要同步的脉冲。

用于读取数据并与之同步的代码片段:

register bool done =false;// 等待 500khz 溢出 while (!(TIFR2&_BV(TOV2))) { // 在等待 500khz 脉冲时检测到下降沿。 if ((TCNT0) &&(!done)) { // 检测到脉冲,重置定时器计数器以与脉冲同步 TCNT2=phase; // 等待脉冲再次变高而 (!(PIN_RAW_FLOPPYDATA_PORT &PIN_RAW_FLOPPYDATA_MASK)) {};完成 =真; }}// 重置溢出标志TIFR2|=_BV(TOV2); // 我们是否检测到来自驱动器的脉冲?if (TCNT0) { DataOutputByte|=1; TCNT0=0;} 

我意识到根据在上述循环中执行的指令,脉冲检测和写入之间的时间 TCNT2=phase; 可能会随着执行几条指令所花费的时间而改变。

意识到这可能会导致数据中出现一些错误/抖动,并且在上述循环中,我可能实际上错过了来自驱动器的脉冲(因此错过了重新同步位),我决定从我之前的一个尝试,ISR(中断)。

我将数据脉冲连接到 Arduino 上的第二个引脚。数据现在连接到 COUNTER0 触发器,现在还连接到 INT0 引脚。 INT0 是最高的中断优先级之一,因此应该尽量减少触发器和 ISR 被调用之间的延迟,因为这是我真正感兴趣的唯一中断,其他所有中断都被禁用。

需要做的所有中断就是执行上面的重新同步代码,这将代码更改为如下所示:

// 等待 500khz 溢出 while (!(TIFR2&_BV(TOV2))) {} // 重置溢出标志TIFR2|=_BV(TOV2); // 我们是否检测到来自驱动器的脉冲?if (TCNT0) { DataOutputByte|=1; TCNT0=0;} 

ISR 看起来像这样:(注意我没有使用 attachInterrupt 因为这也增加了调用的开销)。

volatile byte targetPhase;ISR (INT0_vect) { TCNT2=targetPhase;} 

编译这个会产生太多的代码,无法足够快地执行。其实拆机上面产生的:

push r1push r0in r0, 0x3f; 63push r0e 或 r1, r1push r24 lds r24, 0x0102; 0x800102 sts 0x00B2, r24; 0x8000b2 弹出 r24pop r0out 0x3f, r0; 63pop r0pop r1reti 

通过分析代码,我意识到我实际上只需要一些指令。注意到编译器会跟踪我攻击过的任何寄存器,我将 ISR 更改如下:

 volatile byte targetPhase asm ("targetPhase"); ISR (INT0_vect) { asm volatile("lds __tmp_reg__, targetPhase"); asm volatile("sts %0, __tmp_reg__" ::"M" (_SFR_MEM_ADDR(TCNT2)));} 

其中反汇编,产生如下指令:

push r1push r0in r0, 0x3f; 63push r0e 或 r1, r1lds r0, 0x0102; 0x800102 sts 0x00B2, r0; 0x8000b2 弹出 r0out 0x3f, r0; 63pop r0pop r1reti 

指令还是太多了。我注意到编译器添加了很多额外的指令,对于我的应用程序来说真的不需要在那里。所以我查找了 ISR() 并偶然发现了第二个参数 ISR_NAKED。添加这将阻止编译器添加任何特殊代码,但随后我将负责维护寄存器、堆栈和从中断正确返回。我还需要维护 SREG 寄存器,但由于我需要调用的命令都没有修改它,因此我不需要担心。

这将 ISR 代码更改为:

ISR (INT0_vect, ISR_NAKED) { asm volatile("push __tmp_reg__"); // 保留 tmp_register asm volatile("lds __tmp_reg__, targetPhase"); // 将相位值复制到 tmp_register asm volatile("sts %0, __tmp_reg__" ::"M" (_SFR_MEM_ADDR(TCNT2))); // 将tmp_register复制到TCNT2所在的内存位置asm volatile("pop __tmp_reg__"); // 恢复 tmp_register asm volatile("reti"); // 并退出 ISR} 

编译器转换为:

push r0lds r0, 0x0102; 0x800102 sts 0x00B2, r0; 0x8000b2 pop r0reti 

五个指令!完美,或者至少和它一样快,理论上执行需要 0.3125 微秒!这现在应该意味着重新同步应该在脉冲之后的时间一致的周期内发生。下面是正在发生的事情的时序图。这是从没有时钟信号的串行数据馈送中恢复数据的方法:

这稍微改善了结果。它仍然不完美。有些磁盘每次都能完美读取,有些磁盘需要很长时间并且必须不断重试。我不确定这是不是因为一些磁盘已经放置了很长时间,以至于磁性已经降到如此低的水平,以致驱动放大器无法应对。我想知道这是否与 PC 软盘驱动器有关,因此我将其连接到我拥有的外部 Amiga 软盘驱动器,但结果相同。

尝试 6:

我想知道是否还有什么可以做的。也许来自驱动器的信号比我想象的更嘈杂。阅读更多信息后,我发现 1KOhm 上拉电阻是标准配置,可馈入施密特触发器。

在安装了 SN74HCT14N 六角施密特触发器并重新配置草图以在上升沿而不是下降沿触发后,我试了一下,但并没有真正产生任何明显的区别。我猜是因为我每次都在寻找一个或多个脉冲,这可能吸收 任何噪音反正。所以我们将坚持方法 Attempt 5!

sn74hct14.pdf

我的最终面包板解决方案看起来像这样:

请注意,上面的接线与现场草图略有不同。我重新订购了一些 Arduino 引脚以使电路图更容易。

尝试 7:

我对一些我没有读过的磁盘有点不满意。有时磁盘只是没有正确放置在软盘驱动器中。我猜百叶窗上的弹簧没有帮助。

我开始研究检测从磁盘实际接收到的 MFM 数据中是否存在任何错误。

从 MFM 编码的工作规则中,我意识到可以应用一些简单的规则如下:

  • 不能有两个相邻的“1”位
  • 相邻的“0”位不能超过三个

首先,在解码 MFM 数据时,我查看是否连续有两个“1”。如果是,我假设数据随着时间的推移变得有点模糊,并忽略了第二个“1”。

应用此规则后,实际上存在 5 位错误发生的三种情况。这将是我可以改进数据的新领域。

尽管如此,我很惊讶实际上没有检测到那么多 MFM 错误。我有点困惑为什么有些磁盘在没有发现错误的情况下不会读取。

这是一个有待进一步调查的领域。

解码

在阅读了 MFM 的工作原理后,我并不完全确定它是如何正确对齐的。

起初我认为驱动器输出 1 和 0 的开关位。事实并非如此。驱动器在每次相变时输出一个脉冲,即:每次数据从 0 变为 1,或从 1 变为 0。

读完这篇文章后,我想知道是否需要通过将其输入触发器切换来将其转换回 1 和 0,或者读取数据、搜索扇区,如果没有找到,则反转数据并重试!

事实证明,情况并非如此,而且要简单得多。脉冲实际上是原始 MFM 数据,可以直接输入解码算法。现在我明白了这一点,我开始编写代码从驱动器扫描缓冲区并搜索同步字 0x4489。没想到竟然找到了!

从我进行的研究中,我意识到我需要实际搜索 0xAAAAAAAAA44894489 (研究中的一个注释还表明,早期 Amiga 代码中存在一些错误,这意味着未找到上述序列。因此,我搜索了 0x2AAAAAAA44894489 将数据与 0x7FFFFFFFFFFFFFF 进行 AND 运算后 ).

正如预期的那样,我在每个轨道上发现了多达 11 个,对应于 11 个 Amiga 扇区的实际开始。然后我开始读取后面的字节,看看我是否可以解码扇区信息。

我从上述参考资料中提取了一段代码来帮助进行 MFM 解码。没有必要重新发明轮子吧?

读取标题和数据后,我尝试将其作为 ADF 文件写入磁盘。标准的 ADF 文件格式非常简单。它实际上只是按顺序写入的每个扇区(从磁盘的两侧)的 512 个字节。写完后尝试用ADFOpus打开,结果好坏参半,有时打开文件,有时失败。数据明显有错误。我开始查看头部中的校验和字段,拒绝校验和无效的扇区并重复读取,直到我有 11 个有效的扇区。

对于某些磁盘,第一次读取时全部为 11,有些磁盘也进行了多次尝试和不同的相位值。

最后我设法编写了有效的 ADF 文件。有些磁盘需要很长时间,有些甚至是 Amiga 读取它们的速度。不再有工作的 Amiga 我实际上无法检查这些磁盘是否正常读取,它们已经存放在阁楼的一个盒子里多年了,所以很可能已经降级了。

那么接下来是什么?

下一步已经发生 - V2 在这里可用 并改进了读写支持!

嗯,首先,我在 GNU 通用公共许可证 V3 下使整个项目免费和开源。如果我们想要保留 Amiga 的任何希望,那么我们不应该为了特权而互相剥削,此外,我想回馈我曾经工作过的最好的平台。我也希望人们能够开发它并进一步发展并继续分享。

我接下来想看看其他格式。 ADF 文件很好,但它们只适用于 AmigaDOS 格式的磁盘。有很多具有自定义复制保护和非标准扇区格式的标题,这种格式根本无法支持。

根据维基百科,还有另一种磁盘文件格式,即 FDI 格式。一种有据可查的通用格式。这种格式的优点是它尝试尽可能接近原始存储轨道数据,因此希望能解决上述问题!

我还遇到了软件保护协会,特别是 CAPS(正式名称为 Classic Amiga Preservation Society ) 和它们的 IPF 格式。看了一小会,很失望,都关门了,感觉就是用这种格式卖读盘硬件。

所以我的重点将放在 FDI 上!格式。我唯一关心的是数据完整性。不会有任何校验和供我检查以查看读取是否有效,但我有一些想法可以解决这个问题!

最后,我还将考虑添加一个写磁盘选项(可能支持 FDI 和 ADF),因为添加它真的不应该那么难。

代码

GitHub 存储库
Arduino草图和windows源代码https://github.com/RobSmithDev/ArduinoFloppyDiskReader

示意图


制造工艺

  1. 动画
  2. 软盘
  3. Arduino Spybot
  4. FlickMote
  5. 自制电视 B-Gone
  6. 主时钟
  7. 找到我
  8. Arduino Power
  9. Tech-TicTacToe
  10. Arduino Quadruped
  11. Arduino Joystick
  12. Arduino Amiga 软盘阅读器 (V1)