Source file: /~heha/basteln/PC/usbfloppy/usbfloppy.zip/mfm.S

#define __SFR_OFFSET 0
#include <avr/io.h>
	nop
/* MFM-Kodierung (wird bei ED gar nicht verwendet??):
	! = Verletzung der Kodierungsvorschrift zur Synchronworterkennung 0xA1
    |                               |                               |
  0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 Datenbits
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
 _____         _____    !    _____     _______       ___     _____     ___     
/     \_______/     \_______/     \___/       \_____/   \___/     \___/   \___/ Magnetfluss
   ___   _____   ___   _____   ___   _   _____   ___   _   _   ___   _   _   _	       __ __
\_/   \_/     \_/   \_/     \_/   \_/ \_/     \_/   \_/ \_/ \_/   \_/ \_/ \_/ \	Signal RD/WD
       3       4     3       4     3   2       4     3   2   2     3   2   2	Zeiten
       01      01    0       00!   01  1       01    0   0   0     01  1   1	Generierte Bits
 0      1       1    0        0     1  1        0    0   0   0      1  1   1	Merkbits
*/


// 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

// Daten (Sektor oder Vorblock) einlesen
// Die Synchronisation erfolgt nicht an den Nullen, sondern nur am Synchronwort 0xA1.
// Das letzte davon genügt sogar, die Mehrfachheit wird an der Verletzung der MFM-Kodierung erkannt.
// Unerwarteter Abbruch wenn R27:R26 am Ende nicht Null ist:
// Zu kleiner oder zu großer Impulsabstand.
// Kein Abbruch bei nicht auffindbarem Synchronwort: Watchdog benutzen!
// Kein Abbruch bei fehlenden Impulsen: Watchdog benutzen!
// Kein Abbruch bei nur (also umlaufendem) Synchronwort: Watchdog benutzen!
// Keine CRC-Prüfung, das muss der Aufrufer erledigen
// R22 = Datenrate: 0 = 1 MBit/s (ED), 1 = 500 kBit/s (HD), 2 = 250 kBit/s (DD), 3 = 125 kBit/s (SD)
// R27:R26 = Datensatzlänge (typ. 515 für DOS-Datensektoren)
// R31:R30 = Pufferbereich (RAM)
// Veränderte Register: Parameter-Register (außer R22) sowie
// R0:	Letzter Timer-Wert für Differenzbildung
// R1 = 0 (wird für Interrupt-Acknowledge benötigt)
// R23:	Datenbyte
// R24:	Capture-Ereignis-Zeitdifferenz in CPU-Takten
// R25:	Datenbitzähler (8..1) / im Suchmodus (negativ)
// Timer1 muss initialisiert sein (Zählerlänge auf Vielfaches von 256, also 8..16 Bit)
// Input-Capture muss auf fallende Flanke konfiguriert sein
// Interrupts gesperrt, außer Watchdog (falls via Interrupt)
// Für ED (R22=0) ist eine gesonderte Programmlogik unten, mit einigen Vereinfachungen
inBlockMFM:
	ldi	r25,1<<ICF1
	mov	r1,r25		;1 Takt für Interrupt-ACK sparen (out vs. sbi)
	out	TIFR1,r1	;Interrupt löschen
	clt			;Im Synchronfeld Nullen detektieren lassen
0:	sbic	TIFR1,ICF1
	 rjmp	0b
	lds	r0,ICR1L	;Ersten Impuls hernehmen
	out	TIFR1,r1	;Interrupt quittieren
	ldi	r25,-1		;Bitzähler im Synchronisiermodus
	tst	r23		;Sonderfall ED?
	brne	6f
	rjmp	12f		;Zur Sonderroutine springen
// Unterprogramme
0:	clt			;0 merken
	rjmp	1f		;"0" ausgeben
// Mittlere Schleife für mittleren Impulsabstand (48 CPU-Takte)
3:	brts	0b
	set			;1 merken, "01" ausgeben
// Vorlaufendes Null-Bit ausgeben,
// nicht auf Synchronwort 0xA1 gucken (kann gar nicht passieren!)
4:	lsl	r23		;Null-Bit
	dec	r25		;Bitzähler
	brne	1f
	st	Z+,r23		;Byte abspeichern wenn Bitzähler Null
	sbiw	r26,1
	breq	9f
	ldi	r25,8
// Innere Schleife für kürzesten Impulsabstand (32 CPU-Takte)
// R25 hält die gesammelten Bits des aktuellen Bytes
// 10 Takte im Abspeichermodus (+ 4 Takte alle 8 Bits)
// Max. 12 Takte im Synchronwortsuchmodus
1:	lsl	r23		;Datenbyte
	bld	r23,0		;T nach Bit 0 kopieren
2:	dec	r25		;Bitzähler
	brpl	9f		;Im Synchronmodus wenn negativ
	ori	r25,0xC0	;Nicht positiv werden lassen
	cpi	r23,0xA1	;Synchronwort 0xA1 vorhanden?
	brne	6f
;	cpi	r25,-4		;Mit der speziellen Null, vier Bits vorher detektiert?
;	brne	6f
	rjmp	8f		;Mit Datenbytes starten
9:	brne	6f
	st	Z+,r23
	sbiw	r26,1
	breq	9f		;geschafft
8:	ldi	r25,8
// Schleife zum Abholen des Input-Capture-Wertes
// Problem: Blockiert ohne Signal ewig. Watchdog verwenden!
// 7 Takte
6:	sbic	TIFR1,ICF1
	 rjmp	6b
	lds	r24,ICR1L
	out	TIFR1,r1
	sub	r24,r0		;Differenz zum vorherigen Wert
	add	r0,r24		;nächster vorheriger Wert
// Ergebnis zurechtschieben
// 3 Takte (bei HD)
	cpi	r22,2
	brcs	7f		;500 kBit/s (HD) — Wert belasssen
	breq	8f		;250 kBit/s (DD) — Wert halbieren
	lsr	r24		;125 kBit/s (SD) — Wert vierteln
8:	lsr	r24
// Input-Capture-Differenz bewerten
// MFM ist RLL(1,3), es gibt daher nur Lauflängen von 2, 3 oder 4.
7:	cpi	r24,24		;Diese Vergleichswerte sind CPU-taktfrequenzabhängig!
	brcs	8f		;Fehler: Abstand zu klein
	cpi	r24,40
	brcs	1b		;5 {Σ 25 Takte} 2 Zeiten: 1 Bit: Sende T Merke T
// Ende der inneren Schleife
	cpi	r24,56
	brcs	3b		;3 Zeiten: T==1 ? Sende 0 Merke 0 : Sende 01 Merke 1
// Ende der mittleren Schleife
	cpi	r24,72
	brcc	8f		;Fehler: Abstand zu groß
// Fall Zeiten = 4
	brts	4b		;4 Zeiten: Sende 0T Merke T
	ldi	r25,0		;Der Fall T==0 darf nur beim A1-Syncbyte passieren
	rjmp	4b
// Ende der äußeren Schleife
// Im Fehlerfall abbrechen oder Synchronbyte weitersuchen
8:	clt
	tst	r25
	brmi	6b
9:	clr	r1
	ret

/*=== ED ===============================================*
 * Hier ist das getrennte Verarbeiten			*
 * von Synchronisierung und Datenlesen erforderlich	*
 *=== ED ===============================================*/

/*=== Synchronisierung ===*/
0:	clt
	lsl	r23
	rjmp	12f		;5-6; nie 0xA1
// Äußere Schleife für maximalen Impulsabstand (32 CPU-Takte)
// Mittlere Schleife für mittleren Impulsabstand (24 CPU-Takte)
3:	brts	0b
	set		
4:	lsl	r23		;3; nie 0xA1
// Innere Schleife für kürzesten Impulsabstand (16 CPU-Takte)
1:	lsl	r23
	bld	r23,0
	cpi	r23,0xA1
	breq	6f		;4 -- es geht mit T=1 ins Datenlesen
12:	sbic	TIFR1,ICF1
	 rjmp	12b
	lds	r24,ICR1L
	out	TIFR1,r1
	sub	r24,r0
	add	r0,r24		;7
	cpi	r24,20
	brcs	1b		;3 → Σ 14 Takte: Okay! Keine Zeit zu kurze Abstände zu detektieren
// Ende der inneren Schleife
	cpi	r24,28
	brcs	3b		;3-1 → Σ max. 19 Takte
// Ende der mittleren Schleife
	cpi	r24,36
	brcs	4b		;generiere 0T
// Ende der äußeren Schleife
	clt
	rjmp	12b
/*=== Datenlesen ===*/
// „Unterprogramme“
0:	clt
	lsl	r23
	rjmp	7f		;Takte: Auf jeden Fall weniger
1:
	st	Z+,r23
	sbiw	r26,1
	breq	9f
	ldi	r25,8
	rjmp	1f		;(9) - alle 8 Bits
2:
	st	Z+,r23
	sbiw	r26,1
	breq	9f
6:	ldi	r25,8
	rjmp	2f		;(9) - alle 8 Bits
// Äußere Schleife für maximalen Impulsabstand (32 CPU-Takte)
// Mittlere Schleife für mittleren Impulsabstand (24 CPU-Takte)
3:	brts	0b
	set		
4:	lsl	r23
	dec	r25
	breq	1b		;5
// Innere Schleife für kürzesten Impulsabstand (16 CPU-Takte)
1:	lsl	r23
	bld	r23,0
7:	dec	r25
	breq	2b		;4
2:	sbic	TIFR1,ICF1
	 rjmp	2b
	lds	r24,ICR1L
	out	TIFR1,r1
	sub	r24,r0
	add	r0,r24		;7
	cpi	r24,20
	brcs	1b		;3 → Σ 14 Takte: Okay
// Ende der inneren Schleife
	cpi	r24,28
	brcs	3b		;3-1 → Σ max. 21 Takte
// Ende der mittleren Schleife
	cpi	r24,36
	brcc	9f		;Abstand zu groß
	brtc	12b		;Zurück zur Synchronisierung (mit T=0)!
	rjmp	4b		;6-1 → Σ 28 Takte: Genug Platz (32 Takte)
// Ende der äußeren Schleife
9:	clr	r1
	ret
Detected encoding: UTF-80