USB-Floppy: MFM schreiben

Kommen die Daten mit Bit 0 oder Bit 7 zuerst? Ich gehe mal davon aus, dass MSBfirst richtig ist.

Ausgangspunkt ist ein ATmega32U4 mit 16-MHz-Quarz, verbaut in Arduino Leonardo und Nachfolgern, etwa dem sehr preiswerten Pro Micro. Für die zeitkritischen Routinen kommt nur Assembler in Frage. Zur Kombination mit gcc wird der Gnu-Assembler-Dialekt gas verwendet, nicht avrasm.

Via SPI

Bei SPI bin ich mir nicht sicher, ob der Controller die Bytes lückenlos im Takt ausgibt; es ist dem SPI-Interface freigestellt, die Zeit zwischen zwei Bytes zu verlängern. Daher den alten Text hier aufklappen:

Für das SPI eines AVR, ich sage mal ATmega32U4 wie auf Arduino Leonardo, ist es kein Problem, den seriellen Bitstrom mit 1 MBit/s zu generieren. Die Firmware müsste:

Via PWM — äh — Output Compare

Die Regel zum Generieren von MFM ist einfach (siehe Grafik):

Die Zeit für jedes L und H beträgt 1 µs für jedes der Symbole bei Aufzeichnung mit 500 kBit/s = HD. Bei 16 MHz Systemtakt hat man pro Bit 32 Takte Zeit. Bei geringerer Datenrate entsprechend mehr. Daher die Idee, das Ganze mit dem PWM-Generator im Non-PWM-Modus generieren zu lassen. Das erspart es, CPU-Takte exakt abzählen zu müssen und verschafft eine zeitliche Pufferzone von 16 CPU-Takten, die für andere Aufgaben — außer zu warten — genutzt werden kann. Etwa dazu, die USB-OUT-Pipe zu bedienen, um weitere Sektordaten vom USB-Hostcontroller (PC) entgegennehmen zu können. Der Non-PWM-Modus ist erforderlich, damit der OCR-Wert sofort und nicht erst beim Zählerüberlauf durchgreift. Der nachfolgende Quelltext-Schnipsel erwartet folgende Randbedingungen:

Für ED (1 MBit/s) wird's eng, aber prinzipiell sollte es trotzdem funktionieren.

// Als Makro, Unterprogrammaufrufe sind beim AVR lahm und fressen 9 Takte
.macro timed_toggle
	sts	OCR1AL,r0	;in Zukunft toggeln lassen
9:	sbic	TIFR1,OCF1A
	 rjmp	9b		;warten bis es getoggelt hat
	sbi	TIFR1,OCF1A	;Interrupt löschen
.endm

// Vorbereiteten Datenblock mit Datenadressmarke (vorn) und CRC (hinten) ausgeben
// R22 = Datenrate 0=ED, 1=HD, 2=DD, 3=SD
// R24 = Anzahl der Taktsynchronbits (Nullen), typisch 12×8 = 48
// R27:R26 = Länge (typisch 515) = Anzahl Datenbytes
// R31:R30 = Adresse im RAM (Zeiger Z)
// 3 A1-Synchronbytes werden automatisch ausgegeben
// Verwendete Register: R0,R1=0,R22,R24,Parameter-Register
outBlockMFM:
	sts	OCR1AH,r1	;High-Schattenregister sicherheitshalber löschen
// Addierwert aus Datenrate errechnen
	ldi	r25,4
	inc	r22
0:	lsl	r25
	dec	r22
	brne	0b
	mov	r1,r25
// Zähler initialisieren und starten
;	ldi	r25,0b00000001
;	sts	TCCR1B,r25	;Zähler starten (falls erforderlich)
	lds	r0,TCNT1L
	sts	OCR1AL,r0	;„ferne Zukunft“ initialisieren
	ldi	r25,0b01000000
	sts	TCCR1A,r25	;„Toggle OC0A“ aktivieren
// hier sicherheitshalber Pin auf High stellen - am besten weglassen
// und in der Mikrocontroller-Initialisierungroutine 1× erledigen
	ldi	r25,0b10000001
	sbis	PINB,5
	 sts	TCCR1B,r25	;Force Output Compare = Flipflopzustand kippen
// Hier Schreibfreigabe aktivieren
	cbi	PORTE,6
// Taktsynchronbits
0:
	rcall	6f
	dec	r25
	brne	0b
// 3 Rahmensynchronbytes (A1)
	ldi	r25,3
	add	r0,r1
0:	rcall	6f		;1 fertig
	add	r0,r1		;0 fertig
	add	r0,r1
	rcall	6f		;1 fertig
	add	r0,r1		;0 fertig
	rcall	6f		;0 fertig
	add	r0,r1
	add	r0,r1		;spezielle 0 fertig
	rcall	6f		;0 fertig
	add	r0,r1
	rcall	6f		;1 fertig
	dec	r24
	brne	0b
	clt			;„vorheriges Bit“ löschen
2:
	ld	r24,Z+		;Byte aus RAM laden
	ldi	r25,8		;Bitzähler
3:
	sbrc	r24,7
	 rjmp	7f
	brts	1f
// 0-Bit: L-H ausgeben
	timed_toggle		;L ausgeben lassen
	add	r0,r1
	timed_toggle		;H ausgeben lassen
	rjmp	0f
// 0-Bit: H-H ausgeben (= nichts tun als 2 Schritte warten)
1:
	add	r0,r1
0:	add	r0,r1
	clt
	rjmp	8f
// 1-Bit: H-L ausgeben: Wie L-H ausgeben aber vorher addieren
7:
	set			;„Vorheriges Bit“ setzen
	add	r0,r1		;später
	timed_toggle		;L ausgeben lassen
	add	r0,r1
	timed_toggle		;H ausgeben lassen
8:	lsl	r24
	dec	r25
	brne	3b
	sbiw	r26,1
	brne	2b
// Hier Schreibfreigabe wegnehmen
	sbi	PORTE,6
	clr	r1
	sts	TCCR1A,r1	;nicht mehr toggeln
;	sts	TCCR1B,r1	;Zähler stoppen (falls erforderlich)
	ret

// Als Unterprogramm, wenn Zeit vorhanden ist
// (sollte sogar für HD, nicht aber für ED gehen; ist aber schwer einzuschätzen)
6:
	timed_toggle
	add	r0,r1
	timed_toggle
	add	r0,r1
	ret

Nur mit Schreiben kann man eine Diskette nur formatieren, nicht beschreiben! Denn zum Sektorschreiben muss der beim Formatieren aufgebrachte Sektor-Header gelesen werden und dann fix auf Schreiben umgeschaltet werden.

Anmerkung

Daten unzerhackt

Während Daten geschrieben werden sollten die Daten vom USB-Out-Endpoint ausgelesen werden, um Sektorlängen zu ermöglichen, die größer als der RAM-Speicher des ATmega32U4 sind. (Diese gibt es beim Amiga: 1 Sektor pro Spur=Zylinder und Kopf.) Während des Sektorlesens kann — außer bei ED — der USB-Endpoint während des Wartens auf die Flanke bedient werden.