- เป้าหมายของพี่: สร้างวิธีง่ายๆ ราคาถูก และโอเพ่นซอร์ส เพื่อกู้ข้อมูลจากฟล็อปปี้ดิสก์แบบ Double Density (DD) ของ Amiga บน Windows 10 และระบบปฏิบัติการอื่นๆ
- วิธีของพี่: ใช้ Arduino + แอปพลิเคชันบน Windows
- ทำไมต้องทำ: เพื่อเก็บรักษาข้อมูลจากดิสก์เก่าๆ นี้ไว้ให้คนรุ่นหลัง อีกอย่างพีซีทั่วไปอ่านดิสก์ Amiga ไม่ได้อยู่แล้ว เพราะมันเขียนข้อมูลแบบเฉพาะตัว
เว็บไซต์โปรเจกต์: (ลิงก์ภายนอกถูกลบออกตามกฎ)
นี่คือเวอร์ชัน 1 ของโปรเจกต์นะน้อง (มีเวอร์ชัน 2 ที่อ่านเขียนดีขึ้นอีก!)
ประสบการณ์กับ Amiga
พี่ต้องบอกเลยว่าการทำงานสายนี้ทุกวันนี้ หนี้บุญคุณทั้งหมดอยู่ที่เจ้า Amiga นี่แหละ โดยเฉพาะรุ่น A500+ ที่พ่อแม่ซื้อให้เป็นของขวัญคริสต์มาสตอนพี่อายุ 10 ขวบ ตอนแรกก็แค่เล่นเกม แต่พักหลังเริ่มสงสัยว่ามันทำอะไรได้อีกบ้าง เลยไปลองเล่น Deluxe Paint III แล้วก็เริ่มเรียนรู้การใช้ Workbench ไปด้วย
The Amiga 500 Plus:

ทุกเดือนพี่จะซื้อนิตยสาร Amiga Format ที่ฮิตมากตอนนั้น เดือนนึงมีแถม AMOS (ภาษาคอมตัวนึง) มาให้ด้วย พี่เลยส่งเกมที่เขียนชื่อ In The Pipe Line เข้าประกวดในคอลัมน์ Write A Game In AMOS ของนิตยสาร แล้วก็ติด 1 ใน 12 คนที่ได้รางวัล (ถ้าจำไม่ผิดนะ) แต่ว่าต้องตามทวงรางวัลกันยกใหญ่เลย!
AMOS - The Creator:

พื้นหลัง
ต่อมาพี่ก็ใช้มันทำโปรเจกต์ตอนเรียน GCSE กับ A-Level (ต้องขอบคุณ Highspeed Pascal ที่เข้ากันได้กับ Turbo Pascal บนพีซี)
เอาล่ะ นั่นมันเรื่องนานมากแล้ว ตอนนี้พี่มีดิสก์เก็บเต็มกล่องหลายกล่อง และเจ้า A500+ ก็พังไปแล้ว พี่เลยคิดว่าจะแบ็กอัพข้อมูลจากดิสก์พวกนี้ลงคอมพิวเตอร์ซะ ทั้งเพื่อเก็บรักษาและเพื่อความ nostalgic บ้าง
เว็บ Amiga Forever มีลิสต์วิธีอ่านดิสก์ Amiga ไว้ดีมาก ทั้งแบบใช้ฮาร์ดแวร์เฉพาะ และแบบบังคับไดรฟ์ในพีซีให้ทำงานแปลกๆ – แต่เสียดายที่วิธีพวกนี้ใช้กับฮาร์ดแวร์สมัยใหม่ไม่ได้แล้ว ส่วนตัวอ่านอย่าง KryoFlux หรือ Catweasel ก็ราคาแพงเกินไป พี่แปลกใจมากที่ส่วนใหญ่เป็นระบบปิด (closed source) ซะด้วย
พี่เป็นคนชอบอิเล็กทรอนิกส์มาก และเคยเล่นกับชิป Atmel (AT89C4051) ตอนเรียนมหา'ลัย เลยตัดสินใจลองดู Arduino (ต้องให้เครดิต GreatScott ด้วยที่ทำให้เห็นว่าเริ่มต้นมันง่ายแค่ไหน) แล้วพี่ก็สงสัยว่าเราจะใช้ Arduino อ่านฟล็อปปี้ดิสก์ได้มั้ย?
พี่ก็เลยเสิร์ชหาโค้ด Arduino floppy drive reading หลังจากข้ามโปรเจกต์ที่เอาไดรฟ์ไป เล่นเพลง ไปแล้ว ก็ไม่เจอวิธีที่ใช้การได้จริงๆ มีแต่บทสนทนาในกลุ่มต่างๆ บอกว่ามันเป็นไปไม่ได้ พี่เจอโปรเจกต์นึงที่ ใช้ FPGA อ่านได้ ซึ่งน่าสนใจมาก แต่ไม่ใช่ทางที่พี่อยากไป เลยเหลือทางเลือกเดียวคือ "ต้องสร้างวิธีอ่านดิสก์ Amiga ด้วยตัวเองซะเลย" จัดไปวัยรุ่น!
งานค้นคว้า
ตอนที่พี่เริ่มโปรเจคนี้ พี่ยังไม่รู้เลยว่าฟล็อปปี้ไดรฟ์มันทำงานยังไง และยิ่งไม่รู้เลยว่าข้อมูลถูกเข้ารหัสลงไปยังไง เว็บไซต์พวกนี้แหละที่ช่วยให้พี่เข้าใจว่าเกิดอะไรขึ้นและมันทำงานยังไง:
- techtravels.org (และหน้านี้)
- The .ADF (Amiga Disk File) format FAQ โดย Laurent Clévy
- Amiga Forever
- Wikipedia - Amiga Disk File
- English Amiga Board
- QEEWiki - Counters on the ATmega168/328
- Floppy drive pinout
- List of floppy disk formats
สมมติฐานเบื้องต้น
จากงานค้นคว้า ตอนนี้พี่รู้แล้วในทางทฤษฎีว่าข้อมูลถูกเขียนลงดิสก์ยังไง และดิสก์หมุนยังไง
พี่เริ่มคำนวณตัวเลขบางอย่าง ตามความเร็วที่ดิสก์ความหนาแน่นสองเท่าหมุน (300rpm) และวิธีที่ข้อมูลถูกเก็บ (80 แทร็ก, 11 เซกเตอร์ต่อแทร็ก และ 512 ไบต์ต่อเซกเตอร์, เข้ารหัสด้วย MFM) เพื่ออ่านข้อมูลให้แม่นยำ พี่ต้องสามารถสุ่มตัวอย่างข้อมูลที่ความถี่ 500Khz; ซึ่งถือว่าเร็วมากเมื่อนึกว่า Arduino ทำงานที่แค่ 16Mhz
ในความพยายามต่อไปนี้ พี่จะพูดถึงเฉพาะฝั่ง Arduino เท่านั้นนะ
ความพยายามครั้งที่ 1:
ก่อนอื่นพี่ต้องรวบรวมฮาร์ดแวร์และเชื่อมต่อกับฟล็อปปี้ไดรฟ์ ฟล็อปปี้ไดรฟ์พี่ได้มาจากพีซีเก่าๆ ที่ที่ทำงาน พร้อมกับดึงสาย IDE มาเลย
ด้านล่างคือรูปฟล็อปปี้ไดรฟ์ที่พี่ ปลดปล่อย มันออกมาจากพีซีเครื่องเก่า:

หลังจากศึกษาพินของไดรฟ์ พี่ก็รู้ว่าต้องใช้สายแค่ไม่กี่เส้นจากมัน และหลังจากดูดีๆ ก็พบว่าไดรฟ์นี้ไม่ใช้ไฟเข้า 12v ด้วยซ้ำ
การทำให้ไดรฟ์หมุนทำได้โดยเลือกไดรฟ์และเปิดมอเตอร์ การขยับหัวอ่านก็ง่ายมาก แค่เซ็ตพิน /DIR เป็น high หรือ low แล้วก็ปั๊ลส์พิน /STEP เราสามารถรู้ได้ว่าหัวอ่านมาถึงแทร็ก 0 (แทร็กแรก) แล้วหรือยังโดยดูจากพิน /TRK00
พี่สงสัยเกี่ยวกับพิน /INDEX นี่ มันจะปั๊ลส์หนึ่งครั้งต่อการหมุนหนึ่งรอบ เนื่องจาก Amiga ไม่ใช้สัญญาณนี้เพื่อหาจุดเริ่มต้นของแทร็ก พี่เลยไม่จำเป็นต้องใช้และสามารถปล่อยมันไปได้ หลังจากนั้นก็แค่เลือกว่าจะอ่านด้านไหนของดิสก์ (/SIDE1) และต่อพิน /RDATA เข้าไป
ด้วยความต้องการอัตราข้อมูลที่สูง ความคิดแรกของพี่คือหาทางลดปัญหานี้โดยพยายามลดความต้องการของอัตรานี้ลง
แผนคือใช้ SN74HC594N 2 ตัว เพื่อลดความถี่ในการสุ่มตัวอย่างที่ต้องการลง 8 เท่า พี่ใช้บอร์ดที่อีเบย์เรียกกันว่า Pro Mini atmega328 Board 5V 16M Arduino Compatible Nano (พี่ก็ไม่รู้ว่ามันคืออะไรอย่างเป็นทางการ แต่ใช้กับ Uno ได้นะ!) มาบัฟเฟอร์ข้อมูล ขนาน นี้และส่งไปยังพีซีโดยใช้อินเตอร์เฟซ serial/USART ของมัน พี่รู้ว่าต้องทำให้มันทำงานเร็วกว่า 500Kbaud (รวมโอเวอร์เฮดของ serial ทั้งหมดด้วย)
หลังจากทิ้งไลบรารี serial มาตรฐานของ Arduino ไป พี่ดีใจมากที่พบว่าสามารถ configure the USART on the Arduino at uptp 2M baud และด้วยบอร์ดเบรกเอาท์ F2DI พวกนั้น (อีเบย์เรียกมันว่า Basic Breakout Board For FTDI FT232RL USB to Serial IC For Arduino - ดูด้านล่าง) พี่ก็สามารถส่งและรับข้อมูลที่อัตรานี้ (62.5Khz) ได้อย่างสบายใจ แต่พี่ต้องทำให้มันแม่นยำด้วย
บอร์ด FTDI breakout ที่เข้ากับช่องต่อบนบอร์ด Arduino ได้พอดีเป๊ะ:

เริ่มแรก พี่ใช้ Arduino ตั้งค่า shift register ขนาด 8-bit โดยให้มีแค่ 1 บิตเท่านั้นที่ถูก clock ให้เป็น high สัญญาณที่เหลือรับตรงจากไดรฟ์ฟล็อปปี้เลย (ก็คือแปลงสัญญาณอนุกรมเป็นขนานนั่นแหละ)
นี่คือภาพตอนนั้นที่พี่ต่อบอร์ดเบรดบอร์ดไว้อย่างมั่วซั่ว (แต่มันเวิร์ค!):

พี่ใช้ตัวจับเวลาหนึ่งใน Arduino สร้างสัญญาณ 500Khz ออกมาทางพิน output เพราะฮาร์ดแวร์จัดการให้เลยมันแม่นมาก! – เออ เท่าที่มัลติมิเตอร์พี่วัดได้ก็ตรงเป๊ะ 500Khz นั่นแหละ
โค้ดมันรันได้นะ พี่ clock ข้อมูล 8-bit เต็มที่ 62.5khz ทำให้ CPU ของ Arduino ทำงานไม่หนักเลย แต่ปัญหาคือ... พี่ไม่ได้รับข้อมูลที่มีความหมายสักเท่าไหร่ ตอนนั้นพี่ก็รู้ตัวแล้วว่าต้องไปส่องสัญญาณจริงๆ ที่ออกมาจากไดรฟ์ฟล็อปปี้ให้ละเอียดกว่านี้ พี่เลยไปซื้อออสซิลโลสโคปรุ่นเก่าๆ ราคาถูกมา (Gould OS300 20Mhz Oscilloscope) เพื่อดูว่าเกิดอะไรขึ้น
ระหว่างรอออสซิลโลสโคปมาส่ง พี่ก็ลองวิธีอื่นดูบ้าง
โค้ดส่วนที่ใช้อ่านข้อมูลจาก shift register:
void readTrackData() {
byte op;
for (int a=0; a<5632; a++) {
// We'll wait for the "byte" start marker
while (digitalRead(PIN_BYTE_READ_SIGNAL)==LOW) {};
// Read the byte
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);
// Wait for high to drop again
while (digitalRead(PIN_BYTE_READ_SIGNAL)==HIGH) {};
}
}
ลองรอบสอง:
พี่คิดว่า shift register แม้จะเป็นไอเดียที่ดี แต่อาจจะไม่ช่วยเท่าไหร่ พี่อ่านข้อมูล 8-bit ได้ทีเดียวเลยก็จริง แต่พี่ก็เริ่มไม่มั่นใจแล้วว่าทุกบิตถูก clock เข้ามาอย่างถูกต้องรึเปล่า จากที่อ่านเอกสาร มันบอกว่าข้อมูลน่าจะเป็นพัลส์สั้นๆ มากกว่าเป็นสัญญาณ high/low ตลอด
พี่เลยถอด shift register ออก แล้วลองดูว่าจะเกิดอะไรขึ้นถ้าพี่ใช้ Interrupt (ISR) ตรวจหาพัลส์จากไดรฟ์ โดยใช้สัญญาณ 500Khz ที่ตั้งไว้ก่อนหน้า พี่ตั้งค่า Arduino ใหม่ให้สร้าง ISR หลังจากที่แก้ปัญหาพวกไลบรารี Arduino มายุ่ง (จนได้ใช้ ISR ที่ต้องการ) พี่ก็ย้ายไปใช้ Timer 2
พี่เขียน ISR สั้นๆ ที่จะ shift left บิตในตัวแปร global 1 ไบต์ไปหนึ่งบิต แล้วถ้าพินที่ต่อกับเส้นข้อมูลฟล็อปปี้ดิสก์เป็น LOW (พัลส์มันเป็น low-going) พี่ก็จะ OR 1 เข้าไป ทุกๆ 8 ครั้งที่ทำแบบนี้ พี่ก็จะเขียนไบต์ที่ได้ลง USART
ผลคือ... มันไม่เป็นไปตามคาด! Arduino เริ่มทำงานแปลกๆ และไม่穩定 มาก พี่รู้ตัวเร็วเลยว่า ISR ใช้เวลา execute นานกว่าเวลาระหว่างการเรียกมันซะอีก พี่อาจได้รับพัลส์ทุก 2µSec และจากความเร็วของ Arduino ถ้าพี่สมมติหยาบๆ ว่า คำสั่ง C ทุกคำสั่ง แปลงเป็น 1 รอบเครื่อง (clock cycle) ใน machine code พี่คำนวณได้ว่ามีเวลาสูงสุดแค่ 32 คำสั่งเท่านั้น แย่กว่านั้น คำสั่งส่วนใหญ่ใช้มากกว่า 1 คำสั่ง และหลังจากค้นกูเกิลพี่ก็รู้ว่า overhead การเริ่ม ISR มันสูงมากอยู่แล้ว ไม่ต้องพูดถึงฟังก์ชัน digitalRead ที่ช้ามากๆ
พี่เลยทิ้งฟังก์ชัน digitalRead แล้วหันไปใช้การเข้าถึงพินพอร์ตโดยตรงแทน! แต่มันก็ยังไม่เร็วพอและไม่ช่วยอยู่ดี ไม่ยอมแพ้ง่ายๆ พี่เลยเก็บวิธีนี้ไว้ก่อน แล้วตัดสินใจลองวิธีอื่นต่อไป สู้งานนะน้อง!
ตอนนี้เครื่องออสซิลโลสโคป (Oscilloscope) ที่สั่งซื้อไว้ก็มาส่งแล้วววว! และมันก็ใช้งานได้ด้วย! เป็นรุ่นโบราณสุดๆ ตัวน่าจะแก่กว่าพี่อีกนะ แต่ยังทำงานได้เป๊ะเวอร์ (ถ้าไม่รู้ว่าออสซิลโลสโคปคืออะไร ลองไปหาดูคลิปสอนเบื้องต้นได้ มีในเน็ตเต็มไปหมด วัยรุ่นสายอิเล็กฯ ควรรู้จักเครื่องนี้ไว้!)
เจ้าเครื่องออสซิลโลสโคปเก่าเก็บรุ่น Gould OS300 20Mhz ของพี่:

หลังจากต่อสัญญาณ 500Khz เข้าช่องหนึ่ง และเอาต์พุตจากฟล็อปปี้ดิสก์ไดรฟ์เข้าอีกช่อง มันก็เห็นชัดเลยว่ามีอะไรผิดปกติ สัญญาณ 500Khz เป็นคลื่นสี่เหลี่ยมสมบูรณ์แบบเมื่อใช้เป็นทริกเกอร์ แต่สัญญาณข้อมูลจากฟล็อปปี้ดิสก์นั้นกระจัดกระจายไปหมด พี่เห็นพัลส์นะ แต่มันเบลอๆ แบบมัวๆ เหมือนกัน ถ้าพี่ลองทริกจากสัญญาณฟล็อปปี้ดิสก์ สัญญาณคลื่นสี่เหลี่ยม 500Khz ก็จะกระเจิงไปหมด ไม่ซิงค์กันเลย
รูปภาพสัญญาณบนหน้าจอออสซิลโลสโคปเมื่อทริกจากสองช่องนี้ จะเห็นว่าในช่องที่ ไม่ได้ ถูกตั้งเป็นทริกเกอร์ จะมีเส้นจางๆ เป็นพันๆ เส้นซ้อนกันอยู่ (ในรูปอาจมองไม่ค่อยชัด):

เมื่อวัดแยกกัน พัลส์จากทั้งสองสัญญาณก็วัดได้ที่ 500Khz ทั้งคู่ ซึ่งมันไม่สมเหตุสมผลเลย ประหนึ่งว่าทั้งคู่ทำงานที่ความเร็วเท่ากัน แต่ดันทริกให้เห็นสัญญาณทั้งสองพร้อมกันไม่ได้ แสดงว่าต้องมีอะไรผิดพลาดแน่ๆ
หลังจากมั่วตั้งค่าทริกเกอร์เลเวลไปพักใหญ่ พี่ก็เจอปัญหาจริงๆ แล้ว สัญญาณจากพี่เป็น 500Khz สมบูรณ์แบบ แต่สัญญาณจากฟล็อปปี้ดิสก์นั้น... พัลส์มันเว้นระยะถูกต้องนะ แต่ไม่ตลอดเวลา! ระหว่างกลุ่มพัลส์จะมี error drift แทรกมา และมีช่องว่างของข้อมูลที่ทำให้สัญญาณหลุดซิงค์โดยสิ้นเชิง
นึกถึงการค้นคว้าก่อนหน้านี้ ไดรฟ์ควรหมุนที่ 300rpm แต่มันอาจไม่เป๊ะ 300rpm เสมอไป บวกกับไดรฟ์ที่เขียนข้อมูลมาก็อาจไม่เป๊ะ 300rpm เช่นกัน แล้วยังมีระยะห่างระหว่างเซกเตอร์ (sector) และช่องว่างของเซกเตอร์ (sector gap) อีก ชัดเจนว่ามีปัญหาเรื่องการซิงโครไนซ์ และการพยายามซิงค์สัญญาณ 500Khz กับฟล็อปปี้ดิสก์ตอนเริ่มอ่านข้อมูลนั้น... ใช้ไม่ได้แน่นอน
พี่ยังค้นพบอีกว่าพัลส์จากฟล็อปปี้ดิสก์นั้นสั้นมากกก (แม้จะปรับได้โดยเปลี่ยนค่าตัวต้านทานพูลอัพ (Pull-up Resistor)) และถ้าไทม์มิ่งไม่เป๊ะพอ อาร์ดุยโนอาจจะตรวจจับพัลส์นั้นพลาดไปเลยก็ได้
สมัยพี่เรียนอยู่มหา'ลัย (University of Leicester) พี่เคยลงวิชาหนึ่งชื่อ Embedded Systems เราเรียนเกี่ยวกับไมโครคอนโทรลเลอร์ตระกูล Atmel 8051 โปรเจกต์หนึ่งให้เรานับพัลส์จากสถานีตรวจอากาศจำลอง (ใช้โรตารีเอ็นโคเดอร์ - Rotary Encoder) ตอนนั้นพี่ใช้วิธีอ่านค่าจากพินเป็นช่วงๆ ซึ่งมันไม่ค่อยแม่นยำเท่าไหร่
อาจารย์ผู้สอนวิชานั้น Prof Pont แนะนำว่าพี่น่าจะใช้คุณสมบัติ ฮาร์ดแวร์เคาน์เตอร์ ของ