Source file: /~heha/basteln/PC/USB2LPT/usb2lpt.zip/src/firmware/USB2LPT.A51

;EZUSB-Firmware für USB2LPT, haftmann#software 07/06
;Bis jetzt wird nur ein dptr benutzt (also kein DPS), nur Registerbank 0
;Zu übersetzen mit ASEM51; EEPROM-Datei erstellen mit
;HEX2BIX -I -V 0x16C0 -P 0x06B3
;Pro OUT-Befehl werden zwei Bytes in die OUT-Pipe geschrieben.
;Für jeden IN-Befehl wird ein Byte [Adresse] in die OUT-Pipe gesetzt,
; als Folge kommt ein Byte [Daten] in die IN-Pipe. Genaueres siehe "upv"!
;Seriennummern-Position: 1 Byte @ FFFFh (alt), 4 Byte @ FFFCh (Intel, neu)
;06xxxx	EEPROM+XRAM-Routinen etwa wie VEND_AX
;060622	Gekaufte VID+PID eingetragen
;060629	Einzelpin-Richtungsumschaltung
;060630 Open-Collector-Simulation (Daten- und Steuerpins)
;060705	Richtungsbit-Simulation korrigiert (stets 0 bei Mode 0 und 2)
;060710	EPP: allgemeine Fehlerbeseitigung, ungetestet
;060711	Schattenregister für Steuerport, Code-Erweiterbarkeit für Anwender
;060809	DirectIo, korrigiertes Open-Collector-Verhalten (Steuerport), EEPROM
;070128	Richtungsbits geändert auf 0=Eingang, 1=Ausgang (wie EZUSB und ATmega)
;Zu tun: 
;-
	$nopaging
	$nosymbols
	$nomod51
	$nolist
	$include(an2131.mcu)	;fest für AN2131SC
	$include(makros.i51)
	$list
	$genonly		;das "Innere" von Makros
	$condonly		;das "Innere" von IFxx

DateYear	equ	2007	;"Versionskennung"
DateMonth	equ	1
DateDay		equ	28
Interfaces	equ	2	;1 = ohne "USB-Druckerunterstützung"
MyID		equ	1	;0 = Cypress-ID (Debuggen mit Cypress-Tools)
Always_Renum	equ	0	;1: Firmware-Update im Gerätemanager versagt

;###############
;## Schaltung ##
;###############
;Datenport  (Basisadresse+0) = PortB (mit gestürzter Bit-Anordnung)
;Statusport (Basisadresse+1) = PortC (Bits 7-6-5-4 auf Portbits 6-7-5-4)
;			   und PortA (Bit  3	   auf Portbit  4)
;Steuerport (Basisadresse+2) = PortC (Bits 3..0 ohne "Dreher" angeschlossen)
BSY	EQU	6	;PinsC.6	(ist mit D6 verbunden)
ACK	EQU	7	;PinsC.7
PE	EQU	5	;PinsC.5	(ist mit D6 verbunden)
ONL	EQU	4	;PinsC.4	D/P	TDO
ERR	EQU	4	;PinsA.4	Ucc	Ucc

SEL	EQU	3	;OutC.3
INI	EQU	2	;OutC.2
AF	EQU	1	;OutC.1
STB	EQU	0	;OutC.0
;D4			;OutB.3		/PROG (zieht TDO auf Low)
;D3			;OutB.4		/CTRL (aktiviert 2 Ausgangstreiber)
;D2			;OutB.5		PROG	TMS
;D1			;OutB.6		CCLK	TCK
;D0			;OutB.7		DIN	TDI
LED	EQU	5	;auf PortA, HIGH-aktiv
;###############
;## Endpoints ##
;###############
;Pipe	Funktion					EZUSB	FX2
;0	OUT-Adressen, OUT-Daten, IN-Adressen		EP2Out	EP2(Out)
;1	IN-Daten					EP2In	EP6(In)
;2(=0)	USB-Druckerunterstützung Vorwärtskanal		EP4Out	EP4(Out)
;3(=1)	USB-Druckerunterstützung Rückkanal		EP4In	EP8(In)
;-----------
;## DATEN ##
;-----------
DSEG AT 20h
IntReq:		ds	1	;für USB-Interruptrequests, bitadressierbar
Configuration:	ds	1	;hier: Null (adressiert) oder Eins (konfig.)
AltSetting1:	ds	1	;Null oder Eins (nur für 2. Interface)
bits:		ds	1
LedBlink	BIT	bits.0	;1 solange die LED blinkt
FeatureChanged	BIT	bits.4	;Feature-Byte in EEPROM brennen (persistent)
Data5V		BIT	bits.6	;wie OCData && !direction
Control5V	BIT	bits.7	;wie (SPP && !TPControl) || OCControl
;===Druckerport===
DCR:		ds	1	;Device Control Register (+2)
ackIntEn	BIT	DCR.4	;1=ein, rücklesbar, aber nicht unterstützt
direction	BIT	DCR.5	;0=OUT, 1=IN, rücklesbar nur bei ECR_Bits>1
ECR:		ds	1	;Extended Control Register (ECP 402)
fifoe		BIT	ECR.0	;1 wenn FIFO leer
fifof		BIT	ECR.1	;1 wenn FIFO voll
ECR_Bits:	ds	1	;Mit immer nur einem Bit gesetzt (one-hot)
;0=SPP,1=BiDi,2=SPP-FIFO,3=ECP,4=EPP,5=?,6=Test,7=Config
EPPTimeOut:	ds	1	;Bit0=0: kein TimeOut aufgetreten
				;Bit2=0: Interrupt aufgetreten (zz. ungenutzt)
Feature:	ds	1
OCData		BIT	Feature.0 ;Offene-Senke-Simulation für Daten (+0)
TPControl	BIT	Feature.1 ;Totempfahl auch bei SPP (hier immer 1)
OCControl	BIT	Feature.2 ;Offene-Senke-Simulation für Steuerport (+2)
DirectIo	BIT	Feature.6 ;keine Invertierungen, kein Datenrichtungsbit
;===I²C===
I2C_ALen:	ds	1	;entspricht etwa wIndexH beim Vendor-Request A2
 I2C_ALen0	BIT	I2C_ALen.0	; 1 = 1 Byte, oder 2 Bytes vertauscht
 I2C_ALen1	BIT	I2C_ALen.1	; 1 = 2 Bytes (sonst 0 oder 1 Byte)
 I2C_NoStop	BIT	I2C_ALen.2
 I2C_NoStart	BIT	I2C_ALen.3
 I2C_Paging	BIT	I2C_ALen.4	; 1 = Paging aktiv (nicht wIndexH)
 I2C_Verify	BIT	I2C_ALen.5	; zusammen mit Paging immer Verify

DSEG AT 30h
FIFOSIZE	equ	16
;===FIFO===... hat hier eine Tiefe von FIFOSIZE Wörtern zu je 9 Bit
Fifo:		ds	FIFOSIZE*2	;Wegen ECP brauchen wir 9 bit Breite!
fifor:		ds	1	;Fifo-Lesezeiger
fifow:		ds	1	;Fifo-Schreibzeiger
;===allgemein===
LedTime:	ds	1	;"Nachblinkzeit" der LED in ms
BlinkF:		ds	1	;"Blinkfrequenz" in ms (halbe Periode)
Blink:		ds	1	;Blink-Zähler
UniIdx:		ds	1	;Index in OUT4BUF zum byteweisen Lesen
;=== Variablen für I²C/EEPROM ===
I2C_Addr:	ds	1	;I²C-Adresse (EEPROM)
I2C_PMask:	ds	1	;Für seiten-weises EEPROM-Schreiben,
	;00 = einzelbyteweise, 07 = 8-Byte-Seite, 1F = 32-Byte-Seite usw.
	;07 (8bit), 1F (16bit) oder (1<<High-Nibble von wIndexH)-1 (Request ED)
;Die PMask wird mit dem LOW-Teil der laufenden Adresse geODERt; bei
;Ergebnis Null wird der I²C-Transfer unterbrochen und das Brennen ausgelöst.


;--------------
;## PROGRAMM ##
;--------------
;--> Dieses Programm kommt beinahe ohne Interrupts aus! <--
;Nach dieser Fallstudie frage ich mich, wieso Anchor/Cypress dem Anwender
;einen Bären mit 'zig Interrupts aufbinden will, wobei die Beispielquellen
;nichts anderes machen als in der ISR ein Bit zu setzen und im Hauptprogramm
;abzufragen (Polling). Dazu sind doch Interrupt-Request-Bits da!
;Nur für's Resume [Fortsetzen] schien eine Mini-ISR unumgänglich zu sein.
;(Naja, Schlafmodus und Resume sind hier ganz nettes Beiwerk...)

CSEG AT 0
	ljmp	main		;am Reset-Vektor
SAVEORG 6,0
	dwi	%(DateYear-1980)*512+DateMonth*32+DateDay

SAVEORG 2Eh
Scratch:ds	1	;für EEPROM-Transfer
usr2f:	RETC	;@002F Ansprung bei Ausgabebyte > 20h, ersetzen mit AJMP
usr31:	RETC	;@0031 unbekannter Request über EP0, ersetzen mit AJMP

SAVEORG RESUME	;RESUME-ISR @33h (erforderlich, sonst geht das Wecken nicht)
	clr	EICON.4
	reti
usr36:	ret	;@0036 Zyklischer Ansprung, ersetzen mit AJMP (oder LJMP)

SAVEORG 0C00h			;3 Kilobyte für User
;Erzeugt Bit-Reversions-Tabelle wegen "gestürzter" Zuordnung
;der Datenbits zum Parallelport (um Layout zu vereinfachen)
;VR: DPTR=BitReverse+256,A=0,B=0FFh
MakeBitReverse:
	mov	dptr,#BitReverse
	clr	a
mbr:	rlc	a
	mov	B.0,C
	rlc	a
	mov	B.1,C
	rlc	a
	mov	B.2,C
	rlc	a
	mov	B.3,C
	rlc	a
	mov	B.4,C
	rlc	a
	mov	B.5,C
	rlc	a
	mov	B.6,C
	rlc	a
	mov	B.7,C	;24 Takte
	rlc	a
	xch	a,B	;jetzt in B das ursprüngliche A
	STOS		;abspeichern
	xch	a,B
	inc	a
	jnz	mbr
	ret
;================================
;== Zeichenketten-Umwurstelung ==
;================================
ReadUTF8:
;UTF8/8859-1 -> Unicode-Konvertierung (Unicode-16 für USB)
;Um das EEPROM-File kleiner und den Quelltext leichter pflegbar zu machen...
;PE: dptr=Quell-UTF8; jedes "falsche" UTF8 wird als ISO-8859-1 interpretiert!
;    (Damit kann man in Westeuropa immer mit ISO-8859-1 arbeiten.)
;    DPS.0=0!
;PA: r7:r6=Unicode-16
;    dptr vorgerückt
;VR: A,R4,R5,R6,R7,dptr,Flags
;LZ: Stark vom Zeichensatz abhängig, bei reinem ASCII 14 Takte
	LODS
	mov	r6,a		;R6 enthält fortan erstes Byte für Rückfall
	jnb	ACC.7,utf1	;normales ASCII
	jnb	ACC.6,utf1	;kein gültiges UTF8-Führungsbyte
	jnb	ACC.5,utf2	;2-Byte-UTF8
	jb	ACC.4,utf1	;kein 3-Byte-UTF8
utf2:	anl	a,#3Fh
	jz	utf1
	dec	a
	jz	utf1
	inc	a
	rr	a
	rr	a
	mov	r5,a		;geANDetes und geschobenes Führungsbyte
	LOADX
	jnb	ACC.7,utf1	;kein gültiges Trail-Byte
	jb	ACC.6,utf1	;ebenso
	clr	ACC.7
	xch	a,r5		;R5=geANDetes Folgebyte, A=höheres Byte
	mov	r4,a		;R4=höheres Byte (Bits 7:6 oben, 10:8 unten)
	anl	a,#0C0h
	orl	a,r5
	xch	a,r4		;R4=niederwertiges Byte, A=höheres Byte
	anl	a,#0Fh		;3 Datenbits 10:8 und 1 "Testbit"
	mov	r7,a		;R7 setzen (höheres Byte)
	jnb	ACC.3,okay2	;war 2-Byte-Kode
	push	DPL0
	push	DPH0		;retten zur Restaurierung (fehlt "dec dptr")
	 LOADX	NEXT		;drittes Byte
	 jb	ACC.6,utf1r	;kein Folgebyte, ganze Kette verwerfen
	 jbc	ACC.7,utf3	;und #0:R6 (»Windows-Zeichensatz«) annehmen
utf1r:
	pop	DPH0		;dptr zurückstellen
	pop	DPL0
utf1:	mov	r7,#0
	ret
utf3:
	 mov	r5,a
	 mov	a,r4		;R5=geANDetes drittes Byte, A="mittleres" Byte
	 rr	a
	 rr	a
	 mov	r4,a		;R4=rechtsgeschobenes "mittleres" Byte
	 anl	a,#0C0h		;Bit 7:6
	 orl	a,r5		;A=Bit 7:0
	 xch	a,r4		;Low-Teil fertig, in R4, A="mittleres" Byte
	 anl	a,#3Fh		;Bit 13:8
	 xch	a,r7		;nach R7, A=höchstes Byte (nur 2 Bits 15:14)
	 rr	a
	 rr	a
	 anl	a,#0C0h
	 orl	a,r7		;High-Teil fertig
	 mov	r7,a		;in R7
	 add	a,#-8
	 jnc	utf1r		;zu klein, ungültig!
	pop	ACC
	pop	ACC
okay2:	mov	a,r4
	mov	r6,a		;R6:=R4 (Low-Teil) setzen
	inc	dptr
	ret

MakeString:	;USB-String-Deskriptor (erst) bei Anforderung zusammenbauen
;PE: R2=String-Nummer (in nullterminierter Kette)
;PA: StringPuffer gefüllt mit USB-String-Deskriptor
;VR: R1=0,R2=0,R4,R5,R6,R7,dptr
;LZ: Etwa linear mit R2 zunehmend!
	mov     dptr,#StringDescriptors
	inc	r2
	sjmp	ms2
ms1:	LODS
	jnz	ms1
ms2:	djnz	r2,ms1
	push	MPAGE
	 mov	MPAGE,#HIGH(StringPuffer)
	 mov	r1,#0
	 mov	r7,#3		;"String-Deskriptor"
ms3:	 mov	a,r6
	 movx	@r1,a		;(zunächst wird eine falsche Länge gesetzt)
	 inc	r1
	 mov	a,r7
	 movx	@r1,a		;"Zeichen", High-Byte
	 inc	r1
	 acall	ReadUTF8
	 mov	a,r6
	 orl	a,r7
	 jnz	ms3
	 mov	a,r1
	 mov	r1,#0
	 movx	@r1,a		;Länge in Bytes nachtragen
	pop	MPAGE
	ret

;EEPROM-Adresse (gerade; Schreibadresse) am I²C-Bus
EADDR	equ	0A0h		;für 8-bit-EEPROM, 16-bit-EEPROMS haben EADR+2

;EEPROM-Default-"Seitengröße" zum Brennen mehrerer Bytes auf einmal
EPAGE8	equ	8		;für 8-bit-EEPROM 24LC01/02
EPAGE16	equ	32		;für 16-bit EEPROM 24LC32/64
;Andere EEPROM-Typen müssen per wIndex ausgewählt werden.
;Eine "Seitengröße" von 1 führt zum Einzelbyte-Brennen (viel langsamer)

;Die Auswahl 8/16 bit erfolgt automatisch anhand I2CS.4 wie im EZUSB-Kern

;======================
;== Byte-Ein/Ausgabe ==
;======================

i2c_wr:	;Byte auf I²C schreiben (kann blockieren!)
;PE: A=Ausgabe-Byte
;PA: CY=1 bei Fehler oder NAK, ACC.4=ID1
	STX	I2DAT
	dec	r0		;auf I2CS
i2c_w:	LDX
	mov	c,ACC.2		;BERR-Bit
	jc	i2c_err
	jnb	ACC.0,i2c_w	;DONE-Bit
	orl	c,/ACC.1	;ACK-Bit=0 -> C=1
	ret
	
i2c_rd:	;Byte von I²C lesen (kann blockieren!)
;PE: R4=Anzahl noch zu lesender Bytes (für LASTRD und STOP)
;PA: A=Eingabe-Byte
	mov	r0,#LOW(I2CS)
i2c_r:	LDX	
	mov	c,ACC.2		;BERR-Bit
	jc	i2c_err
	jnb	ACC.0,i2c_r	;DONE-Bit
i2c_d:	;Dummy-Lesezugriff (Seiteneinstieg mit A=0 und R0=I2CS)
	jb	I2C_NoStop,i2c_b
	cjne	r4,#2,i2c_a
	setb	ACC.5		;LASTRD setzen
i2c_a:	cjne	r4,#1,i2c_b
	setb	ACC.6		;STOP setzen
i2c_b:	STX			;LASTRD bzw. STOP setzen
	LDX	NEXT		;I2DAT lesen
i2c_err:ret

;==============================
;== I²C(EEPROM)-Adressierung ==
;==============================

EAdr:	;I²C starten und (Adress-)Bytes ausgeben
;PE: DPTR=Adresse (DPL allein, wenn ALEN<2)
;PA: CY=1 bei Fehler
;VR: A,R0,R2
;N: Diese Routine kann bis 10 ms blockieren, bis der EEPROM bereit ist
;   (weil er sich bis zum Fertigschreiben I²C-busseitig tot stellt)
	LDX	I2CS
	jb	ACC.6,EAdr	;Stoppsequenz abwarten
	mov	r2,#0		;max. 256 Versuche, 10 ms = 120000 CPU-Takte
eal:	jb	I2C_NoStart,ea_nostart
	STX	I2CS,80h	;(4) Startsequenz ausgeben
ea_nostart:
	mov	a,I2C_Addr	;(2) I²C-Adresse: Schreiben
	acall	i2c_wr		;(>=25)
	jnc	ea1		;(3) EEPROM könnte mit Brennen beschäftigt sein
	jb	ACC.2,eae	;(3) wenn BERR nicht noch einmal versuchen
	jnb	I2C_Paging,eae	;(3) raus mit Fehler (nicht neu versuchen)
	mov	r0,#144		;(2)
	djnz	r0,$		;(144*3) warten
	djnz	r2,eal		;(3) ... Adressierung wiederholen!
eae:	ret			;raus (ggf. mit Fehler)
ea1:	;Adressbyte-Ausgabe (Tabelle s.u.)
	jnb	I2C_ALen0,ea_x0
	mov	a,DPL0		;EEPROM-Adresse Low-Teil - oder Low-Teil zuerst
	acall	i2c_wr
	jc	i2c_err
	jnb	I2C_ALen1,eae	; kein Fehler: fertig
	mov	a,DPH0		;EEPROM-Adresse High-Teil
	jmp	i2c_wr
ea_x0:
	jnb	I2C_ALen1,eae	; kein Fehler: fertig ohne ein Adressbyte
	mov	a,DPH0		;EEPROM-Adresse High-Teil zuerst
	acall	i2c_wr
	jc	i2c_err
	mov	a,DPL0		;EEPROM-Adresse Low-Teil als zweites
	jmp	i2c_wr		;immer schreiben und Ende
;Adressbyte-Ausgabe tabellarisch
; I2C_ALen	wIndexH	!	 I²C-Ausgabe
;ALen1	ALen0	ALEN	!	1.Byte	2.Byte	
;0	0	0	!	-	-
;0	1	1	!	DPL	-		DPL = wValueL
;1	0	2	!	DPL	DPH		DPH = wValueH
;1	1	3	!	DPH	DPL

;=============================
;== I²C(EEPROM)-Ein/Ausgabe ==
;=============================

EWrite:	;(Boot-EEPROM oder beliebiges) I²C-Gerät schreiben
;PE: DPTR=(EEPROM-)Adresse (Ziel)
;    I2C_Addr = I²C-Adresse
;    I2C_ALen = I²C-Adresslänge sowie übrige Bits
;    I2C_PMask= Seiten-Maske
;    AutoPtr=Puffer-Adresse (zu schreibende Daten)
;    R4=Länge der Daten (0 = keine Daten)
;PA: CY=1 bei Fehler, dann R4=verbliebene Bytes im Puffer
;    DPTR erhöht
;VR: A,DPTR,R0,R2,R4
	acall	EAdr
	jc	ewe
	inc	r4
	sjmp	ewf
ewl:	
	LDX1
	acall	i2c_wr		;1 Byte schreiben (noch nicht brennen)
	jc	ewe
	inc	dptr		;EEPROM-Adresse mitzählen
	jnb	I2C_Paging,ewf
	mov	a,I2C_PMask
	anl	a,DPL0
	jz	ewp		;Seite zu Ende!
ewf:	djnz	r4,ewl		;in Seite, nächstes Byte
	jb	I2C_NoStop,ewr
ewe:	STX	I2CS,40h	;Stoppsequenz
ewr:	ret
ewp:	STX	I2CS,40h	;Stoppsequenz, damit brennen
	djnz	r4,EWrite	;weitermachen, nächster Block
	ret

EE_W:	jnb	I2C_Verify,EWrite	;sofort zum Schreiben gehen!
	push	DPL0		;Adresse zwecks Vergleichen retten
	push	DPH0
	push	AR4
	 acall	EWrite
	pop	AR4
	pop	DPH0
	pop	DPL0		;Adresse zurückstellen
	STXW	AutoPtr,Out0Buf	;auch Vergleichspuffer zurück
	setb	F0
	jnc	ERead		;Vergleichen
	ret

ERead:	;Boot-EEPROM lesen oder überprüfen
;PE: DPTR=EEPROM-Adresse (Quelle)
;    I2C_Addr = I²C-Adresse
;    I2C_ALen = I²C-Adresslänge sowie NoStart und NoStop-Bits
;    AutoPtr=Puffer-Adresse (Lese-Puffer), R1=XAutoDat
;    R4=Länge Daten/Puffer (0 = nicht erlaubt!)
;    F0=0: lesen, F0=1: prüfen/vergleichen (Verify)
;PA: CY=1 bei Fehler, dann R4=verbliebene Bytes im Puffer
;    DPTR erhöht
;VR: A,DPTR,R0,R2,R4
	jb	I2C_NoStart,erns	;Hier: Auch keine Adressbytes ausgeben!
	acall	EAdr
	jc	ere
	STX	I2CS,80h	;Noch eine Startsequenz ausgeben
erns:	mov	a,I2C_Addr	;I²C-Adresse
	inc	a		;Leseadresse
	acall	i2c_wr
	jc	ere
	inc	r4		;Problem: Klappt nicht mit R4=0
	acall	i2c_d		;Dummy-Lesezugriff von I2DAT
	dec	r4
erl:
	acall	i2c_rd		;Byte lesen
	jc	ere
	jb	F0,erv		;Vergleichen
	STX1
	sjmp	er1
erv:	mov	r0,a
	LDX1
	xrl	a,r0		;A=0 wenn gleich
	jnz	eru		;raus mit Fehler wenn ungleich
er1:	inc	dptr		;EEPROM-Adresse mitzählen
	djnz	r4,erl
	jb	I2C_NoStop,er9	;Stopp-Unterdrückung nur bei regulärem Ende!
	db	0E5h		;mov a,xx
eru:	setb	c
ere:	STX	I2CS,40h	;Stoppsequenz
er9:	ret

;===================================
;== Arbeitszellen-Initialisierung ==
;===================================

EReqInit:	;A2-Request auswerten und drei I²C-Parameter zusammenstellen
;PE: wIndex = USB-Parameter (ACHTUNG bei EZUSB Control Panel [Rindfleisch])
;VR: A,R0,I2C_Addr,I2C_ALen,I2C_PMask
	LDX	SetupDat+4	;wIndexL
	jnz	eri_nBootRom
EReqInitBoot:	;Seiteneinstieg für Startup
	LDX	I2CS		;bei wIndexL=0 Boot-ROM automatisch wählen
	mov	c,ACC.4		;ID1-Bit = 1 bei 16-bit-EEPROM
	mov	a,#EADDR
	mov	ACC.1,c		;ggf. aus A0 ein A2 machen
	mov	I2C_Addr,a
	clr	a
	addc	a,#1		;Adresslänge 1 oder 2 Bytes
	mov	I2C_ALen,a
	sjmp	eri_Auto
eri_nBootRom:
	clr	ACC.0		;Hier: Schreibadresse!
	mov	I2C_Addr,a
	LDX	NEXT		;wIndexH
	anl	a,#0Fh		;Low-Nibble
	mov	I2C_ALen,a	;übernehmen, wie es ist
	LDX			;noch einmal wIndexH
	swap	a
	anl	a,#0Fh		;High-Nibble
	jnz	eri_nAuto
eri_Auto:			;bei HINIBBLE(wIndexH)=0 Paginierung automatisch
	mov	a,I2C_Addr
	anl	a,#0F0h
	xrl	a,#0A0h		;Irgendein serieller EEPROM-Typ (Axh)?
	jnz	eri_e		;nein, keine (automatische) Paginierung
	mov	a,I2C_ALen
	cjne	a,#1,eri_n1	;Adress-Länge 1? (Und kein NoStop/NoStart?)
	mov	a,#EPAGE8-1	;Maske setzen
	sjmp	eri_s
eri_n1:	cjne	a,#2,eri_e	;Adress-Länge 2? Alles andere: ohne Auto-Paging!
	mov	a,#EPAGE16-1	;Maske setzen
	sjmp	eri_s
eri_nAuto:
	mov	r0,a		;Bitmaske erzeugen: 1->00, 2->01, 3->03
	clr	a		;4->07, 5->0F, 6->1F, 7->3F, 8->7F, 9->FF
	sjmp	eri_f
eri_l:	setb	c
	rlc	a		;nach links schieben, Einsen einfügen
eri_f:	djnz	r0,eri_l
eri_s:	mov	I2C_PMask,a
	setb	I2C_Paging	;Auto-Paging aktivieren
	setb	I2C_Verify	;Verify aktivieren (vorerst gemeinsam)
eri_e:	RETNC			;(womöglich später auch beim Lesen, wenn erf.)

;=============================
;== EEPROM- und RAM-Zugriff ==
;=============================
MemR:	LODS			;lesen
	STX1
	djnz	r4,MemR
	ret
MemW:	LDX1
	STOS			;schreiben
	djnz	r4,MemW
	ret
	
Partial_64:
;Subtrahiert 64 (bzw. R4) von der Gesamtlänge; R7=0 wenn letzter Block
	mov	r4,#64
Partial_r4:
	mov	a,r6
	subb	a,r4
	jnc	pa1
	clr	c
	djnz	r7,pa1	;Letzte "Runde" mit R7=0
	LD	r4,a,r6
	inc	r4
	ret
pa1:	mov	r6,a
	ret
	
LongEP0:
;Diese Routine funktioniert "absichtlich" nicht bei Längen >=FF00h,
;Control-Transfers sind ohnehin auf 1000h (4KB) beschränkt.
;Behandlung aller "langen" EP0-Transfers, mit selbstmodifizierendem Code
;dptr=Zeiger Leseroutine (R1=AutoPtr-EP0Buf, DPTR=wValue, R4=Länge)
;R7:R6=Zeiger Schreibroutine (dito, für OUT-Transfers)
;Alle diese Routinen bekommen DPTR=wValue, R4=Transferlänge, R1=AutoDat,
;und dürfen R6, R7 nicht verändern. (R7=0 für letzten Teiltransfer)
;F0 ist (zunächst) gelöscht
;R4=bmRequestType
	clr	F0
	mov	a,DPH0
	mov	r5,DPL0	;retten
	mov	dptr,#PatchR+1
	STORX
	mov	a,r5
	STORX	NEXT
	mov	a,r7
	mov	dptr,#PatchW+1
	STORX			;High-Teil
	mov	a,r6
	STORX	NEXT		;Low-Teil
	LDX	SetupDat+2	;wValueL
	mov	DPL0,a
	LDX	NEXT		;SetupDat+3
	mov	DPH0,a
	LDX	SetupDat+6	;wLengthL
	add	a,#0FFh
	mov	r6,a
	LDX	NEXT		;wLengthH
	addc	a,#0		;erhöhen für Funktion von Partial_xx
	mov	r7,a
	cjne	r4,#01000000b,a3_no	;bmRequestType
	jz	LEP0E		;Keine Daten!
LEP0W:	WAIT_EP0_OUT
	mov	r4,a
	call	Partial_R4
	STXW1	AutoPtr,Out0Buf
	inc	r1		;AutoData
PatchW:	lcall	0
	jc	LEP0E
	mov	a,r7
	jnz	LEP0W		;nächste Runde, sowie CY=0
	ret
	
a3_no:	cjne	r4,#11000000b,err2	;bmRequestType
	jz	LEP0E		;Keine Daten!
LEP0R:	call	Partial_64
	WAIT_EP0_IN
	push	AR4		;retten für In0BC
	 STXW1	AutoPtr,In0Buf
	 inc	r1		;AutoData
PatchR:	 lcall	0
	pop	ACC
	jc	LEP0E
	STX	In0BC
	mov	a,r7
	jnz	LEP0R		;nächste Runde, sowie CY=0
LEP0E:	ret
err2:	RETC

;=========================
;== Routinen für Ende 0 ==	;PE: R0=SetupDat, R4=SDAT[0], R5=SDAT[2]
;=========================	                  R6=SDAT[4], R7=SDAT[5]
IF MyID
 ClassOut	equ	00100001b	;Klassenrequest (Drucker)
 ClassIn	equ	10100001b
ELSE
 ClassOut	equ	01000000b	;VendorRequest zum Test im EzMr
 ClassIn	equ	11000000b
ENDIF

epid2r0:
;Rechnet Endpoint-ID in INxCS/OUTxCS-Adresse um
;PE: R6=[SetupDat+4]=Enden-Adresse
;PA: R0=Zeiger auf EPxCS (Low-Teil)
;VR: A,R0,C
	mov	a,r6
	rlc	a		;IN-Bit ausschieben
	cpl	c		;IN-Bit invertieren, also OUT-Bit
	mov	ACC.4,c		;OUT-Bit einsetzen
	anl	a,#1Eh		;Übrige Bits weg
	add	a,#LOW(EP0CS)
	mov	r0,a
	ret

GetStatus:
	cjne	r4,#ClassIn,gs_nid	;GET_DEVICE_ID? (Nur Interface 1)
	jmp	GetDeviceId
gs_nid:
	cjne	r4,#80h,gs_no_dev	;GS_DEVICE
nullin:	clr	a		;kein WakeUp(Bit1), kein SelfPower(Bit0)
wordin:	STX	IN0BUF
	STX	NEXT,0
	STX	IN0BC,2
	ret
gs_no_dev:
	cjne	r4,#81h,gs_no_if	;GS_INTERFACE
	jmp	nullin		;auch zwei Nullen melden
gs_no_if:
	cjne	r4,#82h,ep0stall	;GS_ENDPOINT
	acall	epid2r0
	LDX			;STALL-Bit angeln
	anl	a,#01h
	sjmp	wordin

ClearFeature:
	cjne	r4,#ClassIn,cf_nps	;Klassenrequest
;hier: GetPortStatus
	;cjne	r6,#1,ep0stall
	call	in1
	anl	a,#00111000b
	ajmp	gi		;Info-Byte senden
cf_nps:
	cjne	r4,#0,cf_no_dev	;FT_DEVICE
	cjne	r5,#1,ep0stall	;einziges Feature: Remote Wakeup
	;clr	Rwuen		;Disable Remote Wakeup
	ret
cf_no_dev:
	cjne	r4,#02h,ep0stall
	cjne	r5,#0,ep0stall	;einziges Feature: STALL
	acall	epid2r0
	STX	,0		;STALL entfernen
	mov	a,r6
	mov	c,ACC.7
	mov	ACC.4,c
	anl	a,#00010111b
restog:	STX	TOGCTL
	setb	ACC.5		;WRITEDELAY
	STX			;Togglebit zurücksetzen
	ret

SoftReset:
	cjne	r4,#ClassOut,ep0stall	;nur klassenspezifisch
	;cjne	r6,#1,ep0stall	;nur Interface 1
	STX	OutC,0111b
	clr	ACC.INI
	STX			;RESET-Impuls ausgeben
	setb	ACC.INI
	STX
	ret

SetFeature:
	cjne	r4,#0,sf_no_dev	;FT_DEVICE, nur Rem.Wakeup, nicht unterstützt
	cjne	r5,#1,ep0stall	;einziges Feature: Remote Wakeup
	;setb	Rwuen		;Enable Remote Wakeup
	ret
sf_no_dev:
	cjne	r4,#2,ep0stall	;FT_ENDPOINT
	cjne	r5,#0,ep0stall	;einziges Feature: STALL
	acall	epid2r0
	STX	,01h		;STALL setzen
	ret

GetDescriptor:
	cjne	r4,#80h,ep0stall	;nur DEVICE_IN
	LDX	SetupDat+3
	cjne	a,#01h,gd_no_dev	;GD_DEVICE
	mov	dptr,#DeviceDescriptor
gd_sto:	mov	a,DPH0
	STX	SUDPTRH
	mov	a,DPL0
	STX	NEXT
	ret
ep0stall:
	RETC

gd_no_dev:
	cjne	a,#02h,gd_no_conf	;GD_CONFIGURATION
	mov	dptr,#ConfigDescriptor
	sjmp	gd_sto
gd_no_conf:
	cjne	a,#03h,ep0stall		;GD_STRING
	mov	a,r5
	mov	r2,a
IF Interfaces = 2
	add	a,#-7			;Nur 0..6 zulassen
ELSE
	add	a,#-5			;Nur 0..4 zulassen
ENDIF
	jc	ep0stall
	call	MakeString
	mov	dptr,#StringPuffer
	sjmp	gd_sto

GetConfiguration:
	cjne	r4,#80h,ep0stall	;nur DEVICE_IN
	mov	a,Configuration	 ;Entweder konfiguriert oder unkonfiguriert
gi:	STX	IN0BUF+0
	STX	IN0BC,1
	ret

SetConfiguration:
	cjne	r4,#0,ep0stall		;nur DEVICE_OUT
	mov	a,r5
	add	a,#-2
	jc	ep0stall	;nur Konfiguration 0 oder 1
	mov	Configuration,r5
	ret

GetAltSet:
	cjne	r4,#81h,ep0stall	;nur INTERFACE IN
	cjne	r6,#1,as_n1
	mov	a,AltSetting1
	jmp	gi
as_n1:	jnc	ep0stall	;wenn Interface > 1
	clr	c
	clr	a
	jmp	gi

SetAltSet:
	cjne	r4,#1,ep0stall		;nur INTERFACE OUT
	cjne	r6,#0,as_n0	;Interface 0: h#s Parallelport
ResTog2:	;Data Toggle rücksetzen (EP2)
	mov	a,#02h		;OUT2 rücksetzen
	acall	restog
	mov	a,#12h		;IN2 rücksetzen
	acall	restog
;alle IN-Enden frei machen
	LDX	IN2CS		;BUSY (und STALL) lesen
	STX			;BUSY durch "1" schreiben rücksetzen
;alle OUT-Enden bereit machen
	STX	OUT2BC		;OUT2 scharfmachen
	ret
as_n0:
	cjne	r6,#1,ep0stall	;Interface 1: USB-Druckerunterstützung
	mov	a,r5
	add	a,#-2
	jc	ep0stall	;Nur Alternative 0 oder 1 zulassen
	mov	AltSetting1,r5
ResTog4:
	mov	a,#04h
	acall	restog
	mov	a,#14h
	acall	restog
	LDX	IN4CS
	STX
	STX	OUT4BC
sude:	ret

SUD_Tab:
	ajmp	GetStatus	;0 - auch: GET_DEVICE_ID
	ajmp	ClearFeature	;1 - auch: GET_PORT_STATUS
	ajmp	SoftReset	;2 (normalerweise ep0stall)
	ajmp	SetFeature	;3
err1:	RETC			;4
	RETC	;SetAddress	;5, sollte nicht vorkommen
	ajmp	GetDescriptor	;6
	RETC	;SetDescriptor	;7
	ajmp	GetConfiguration;8
	ajmp	SetConfiguration;9
	ajmp	GetAltSet	;10
	ajmp	SetAltSet	;11
	;RETC	;SyncFrame	;12

HandleSUD:			;liefert CY=1 für EP0Stall
	LDX	SetupDat	;bmRequestType -> R4
	mov	r4,a
	LDX	NEXT		;SetupDat+1: bRequest -> A
	cjne	a,#0A2h,hs1	;EEPROM-Zugriff?
	call	EReqInit	;wIndex heranziehen
	mov	dptr,#ERead
	MOVR	r6,r7,EE_W
	jmp	LongEP0
hs1:	cjne	a,#0A3h,hs2	;XRAM-Zugriff?
	mov	dptr,#MemR
	MOVR	r6,r7,MemW
	jmp	LongEP0
hs2:	mov	r7,a
	add	a,#-12		;nur 0..11 zulassen
	jc	err1
	LDX	NEXT		;SetupDat+2: wValueL -> R5
	mov	r5,a
	LDX	SetupDat+4	;wIndexL -> R6
	mov	r6,a
	LDX	NEXT		;SetupDat+5: wIndexH -> R7
	xch	a,r7
	call	usr31		;darf bei CY=1 kein Register ändern!
	jnc	sude		;bei CY=0 ist's des Users Eigenverantwortung
	JMPTBL	SUD_Tab

;==============================
;== Routinen für Druckerport ==
;==============================
SetECR:	;ECR-Byte setzen, FIFOs leeren
	anl	a,#0F8h
	orl	a,#5		;FIFO leer setzen
	mov	ECR,a
	swap	a
	rr	a
	anl	a,#7
	inc	a
	mov	r3,a
	clr	a
	setb	c
se1:	rlc	a
	djnz	r3,se1		;Bit draus machen, testet sich besser
	mov	ECR_Bits,a

	mov	c,ECR_Bits.4	;EPP-Bit
	mov	a,#0FFh
	subb	a,#0
	mov	EppTimeout,a	;beim Einschalten von EPP auf Null, sonst 1

	mov	a,ECR_Bits
	anl	a,#00000101b	;Bei SPP oder SPP-FIFO auf Ausgabe schalten!
	jz	se3		;kein Schalten am Richtungsbit!
	clr	direction
	call	DirChanged	;Ausgabetreiber stets aktiv - je nach OC-Simul.
se3:	mov	fifor,#Fifo
	mov	fifow,#Fifo
	ret

;===SPP-FIFO===========================
;Die Emulation der SPP-FIFO-Betriebsart
;-> Funktioniert nur mit ausgeschalteter OC-Simulation des Steuerports (+2)
SppXfer:
	jb	fifoe,exf	;wenn FIFO leer ist nichts zu tun
	LDX	PinsC
	jb	ACC.BSY,exf	;wenn beschäftigt dann geht's nicht
	mov	r0,fifor
	mov	AR4,@r0		;Datenbyte->R4
	acall	out0i		;Byte (R4) anlegen, Treiber muss aktiv sein
	LDX	OutC
	clr	ACC.STB
	STX			;Strobe aktivieren
	acall	IncFifoR	;Lesezeiger erhöhen (und Zeit verbrauchen)
	LDX	OutC
	setb	ACC.STB
	STX			;Strobe zurücknehmen
exf:	ret

;===EPP===========================
;-> EPP-Funktionen funktionieren hier nur ohne OC-Simulation.
wait_epp:
;PORTC mit R3 ANDen ausgeben und max. 10 µs auf WAIT=H warten
;sowie Datenrichtung temporär (auf Ausgabe) schalten
;PA: EppTimeOut.0 bei TimeOut, A=abgetastete Portpins
	LDX	OutC		;Zustand lesen
	push	ACC
	 anl	a,r3
	 STX			;Addr/DataStrobe LOW
	 mov	a,r3		;WRITE-Bit (Strobe)
	 rrc	a
	 jc	sw0
	 LDX	OEB		;bei Schreibzugriff: Zustand retten,...
	 push	ACC
	 STX	,0FFh		;temporär alles Ausgabe
sw0:	;max. 10 µs warten bis WAIT=H
	 mov	r3,#7		;7 Runden à knapp 2 µs
	 mov	r0,#LOW(PinsC)
sw1:	 LDX			;(2)
	 jb	ACC.BSY,sw2	;(3) WAIT endlich HIGH
	 djnz	r3,sw1		;(3)
	 setb	EppTimeOut.0	;TimeOut aufgetreten
sw2:	 jc	sw3		;Noch einmal CY auswerten!
	 pop	ACC		;falls Schreibzugriff: OE-Zustand zurück
	 STX	OEB
	 sjmp	sw4
sw3:	 acall	in0		;falls Lesezugriff: Portpins abtasten
	 mov	r3,a
sw4:	pop	ACC
	STX	OutC		;Zustand wiederherstellen
	mov	a,r3		;Im Falle von Schreibzugriff undefiniert!
	ret

out_epp:
;OUT-Befehl Byte R4 mit R3=Addr/DataStrobe, WRITE=0
;Steuerleitungen werden wie im Original nur bei Bedarf nach Low gezogen
;Datenrichtung wird (temporär) umgeschaltet. (Bei "richtigem" Port beobachtet.)
	acall	out0i		;Daten ausgeben (immer - vorher!)
	LDX	PinsC		;EPP 1.9: WAIT muss LOW sein!
	jb	ACC.BSY,no_epp
	jmp	wait_epp

in_epp:
;IN-Befehl mit r3=Addr/DataStrobe
;PA: A=gelesenes Byte
	LDX	PinsC		;EPP 1.9: WAIT muss LOW sein!
	jb	ACC.BSY,no_epp_read_PINSB
	jmp	wait_epp

no_epp_read_PINSB:
	acall	in0		;PinsB schnappen
no_epp:	setb	EppTimeOut.0	;TimeOut-Bit setzen
	ret			;nichts ausgeben!

;===ECP===========================
;-> ECP-Funktionen funktionieren nur, wenn die Hintertüren geschlossen wurden,
;   d.h. alle ungewöhnlichen Datenrichtungen sowie OC-Simulationen
;   ausgeschaltet sind.

IncFifoPtr_Compare:
;FIFO-Zeiger erhöhen und mit anderem Zeiger vergleichen
;PE: R0=Zeiger auf einen der beiden FIFO-Zeiger
;PA: A=0 wenn nach Erhöhung beide Zeiger gleich, sonst <>0
	mov	a,@r0
	inc	a
	inc	a
	cjne	a,#Fifo+FIFOSIZE*2,ic1
	mov	a,#Fifo
ic1:	mov	@r0,a
	mov	a,fifor
	xrl	a,fifow
	ret

IncFifoR:
;FIFO-Lese-Zeiger erhöhen, "FIFO voll" löschen, "FIFO leer" ggf. setzen
;PE: -
;PA: A=0 wenn FIFO leer
;VR: R0,A
	mov	r0,#fifor
	acall	IncFifoPtr_Compare
	jnz	ifr1
	setb	fifoe		;FIFO leer
ifr1:	clr	fifof		;FIFO ist keinesfalls voll
	ret

IncFifoW:
;FIFO-Schreib-Zeiger erhöhen, "FIFO leer" löschen, "FIFO voll" ggf. setzen
;PE: -
;PA: A=0 wenn FIFO voll
;VR: R0,A
	mov	r0,#fifow
	acall	IncFifoPtr_Compare
	jnz	ifw1
	setb	fifof		;FIFO voll
ifw1:	clr	fifoe		;FIFO ist keinesfalls leer
	ret

EcpXfer:
;Auf Transfer von/in FIFO im Hintergrund prüfen
;Diese Routine wird, sofern ECP aktiv, zyklisch aufgerufen
;Keine Parameter, VR: A,R0,R3,R4
	jb	direction,EcpInXfer
	LDX	OutC
	jnb	ACC.STB,oxf2	;2. Phase der Byte-Übertragung zz. aktiv
	jb	fifoe,no_Xfer	;Kann kein Byte rausschicken
	LDX	PinsC
	jb	ACC.BSY,no_Xfer	;Gegenstelle ist beschäftigt
	mov	r0,fifor
	mov	AR4,@r0		;Datenbyte->R4
	inc	r0
	mov	AR3,@r0		;Command/Data->R3
	acall	IncFifoR	;Lesezeiger erhöhen
	acall	out0i		;Byte (R4) anlegen
	STX	OEB,0FFh	;Treiber aktivieren
	mov	a,r3
	rrc	a		;Command(0) oder Data(1) ausschieben
	LDX	OutC
	mov	ACC.AF,C	;HostAck(AF,1) setzen
	setb	ACC.INI		;nReverseRequest auf HIGH (Init) (unnötig?)
	STX
	clr	ACC.STB		;HostClk(STB) auf LOW
	STX
oxf2:	LDX	PinsC		;PeriphAck abfragen
	jnb	ACC.BSY,no_Xfer	;Gegenstelle ist beschäftigt
	LDX	OutC
	setb	ACC.STB		;HostClk(STB) auf HIGH
	STX
	ret
EcpInXfer:
	jb	fifof,no_Xfer	;Kann kein Byte einlesen
	LDX	PinsC
	xch	a,r3
	LDX	OutC
	jb	ACC.AF,ixf2	;HostAck=H, 2. Phase des Byte-Lesens...
	xch	a,r3
	jb	ACC.ACK,no_Xfer	;Gegenstelle meldet (noch) keinen Bedarf
	xch	a,r3
	setb	ACC.AF		;HostAck=H
	STX
ixf2:	LDX	PinsC
	jnb	ACC.ACK,no_Xfer	;PeriphClk=L = noch nicht bereit
	mov	c,ACC.BSY	;Command(0) / Data(1)
	acall	in0		;Datenbyte abholen
	mov	r0,fifow
	mov	@r0,a		;abspeichern
	inc	r0
	clr	a
	mov	ACC.0,c		;Bit einsetzen
	mov	@r0,a
	acall	IncFifoW
	LDX	OutC
	clr	ACC.AF		;HostAck=L
	STX
no_Xfer:ret

DirChanged2:
;Aufzurufen, wenn sich die Steuerportrichtung ändert...
;Neues Control5V berechnen:
	mov	c,ECR_Bits.0	;SPP-Modus?
	anl	c,/TPControl
	orl	c,OCControl
	jc	dc_occontrol1
	jbc	Control5V,dc_occontrol0
	ret
dc_occontrol0:	;OpenCollector-Status löschen
	LDX	OEC
	orl	a,#00001111b	;Alle Ausgänge aktivieren
	STX	OEC
dc_oce:	ret
dc_occontrol1:
	jb	Control5V,dc_oce	;Bereits aktiviert: nichts tun!
	setb	Control5V	;Aktivierung vermerken
	LDX	OutC
DoOcControl:	;A=Datenbyte: Ausgabetreiber nachführen
	cpl	a
	anl	a,#00001111b
	mov	r3,a
	LDX	OEC
	anl	a,#11110000b
	orl	a,r3
	STX
	ret
	
DirChanged0:
;Aufzurufen, wenn sich die Datenportrichtung ändert...
;Neues Data5V berechnen:
	mov	c,OCData	;Feature-Register: aktiv?
	anl	c,/direction	;ECR-Register: Ausgabe?
	jc	dc_ocdata
	jbc	Data5V,DoSetOeb	;OpenCollector-Status löschen
	ret
DoSetOeb:
	clr	a
	jb	direction,dc_tb
	dec	a
dc_tb:	STX	OEB		;Treiberstatus setzen
	ret
dc_ocdata:
	jb	Data5V,dc_oce	;Ist schon an!
	setb	Data5V
	LDX	OutB
;DoOcData:	;A=Datenbyte: Ausgabetreiber nachführen (zu simpel für UP!)
	cpl	a
	STX	OEB		;High-ausgebende Leitungen hochohming
	ret


;===Portzugriffe=========================

;OUT-Unterprogramme bekommen in R4 das Argument,
;IN-Unterprogramme müssen das Byte in A liefern

;=== OUT auf Adresse +0 (Datenport) ===
out0:	mov	a,ECR_Bits
	anl	a,#10110011b	;0 = FIFO-Betriebsarten?
	jz	out0ecp		;mit A=0 in die FIFO
out0i:	mov	a,r4
out0a:	;Innerer Einsprung zur Datenbyte-Ausgabe
	mov	dptr,#BitReverse
	movc	a,@a+dptr
	STX	OutB
	jnb	Data5V,out0e	;Open-Collector-Simulation?
	cpl	a
	STX	OEB
out0e:	ret

;=== OUT auf Adresse +1 (Statusport) ===
out1:	LDX	OutA	;LED-Bit holen
	anl	a,#11101111b
	mov	r3,a
	mov	a,r4
	rl	a		;ERR auf richtige Bit-Position
	anl	a,#00010000b
	orl	a,r3	;übrige Bits einsetzen
	STX
	LDX	OutC	;Steuerbits holen
	anl	a,#00001111b
	mov	r3,a
	mov	a,r4
	rlc	a	;BSY ausschieben
	rl	a	;ACK ans Bit 0
	jb	DirectIo,out1ni	;Nicht invertieren
	cpl	c	;BSY invertieren
out1ni:	rrc	a	;BSY einschieben (künftig Bit 6), ACK ausschieben
	rrc	a	;ACK einschieben (Bit 7)
	mov	c,ACC.0	;Bit0 retten (wegen EPP)
	anl	a,#11110000b
	orl	a,r3
	STX
	anl	c,ECR_Bits.4	;Aufforderung, das TimeOut-Bit zu löschen?
	jnc	out1nepp
	clr	EppTimeOut.0	;TimeOut-Bit löschen
out1nepp:
	ret

;=== OUT auf Adresse +2 (Steuerport) ===
;Bit4=IRQ-Freigabe (nicht unterstützt, aber gespeichert)
;Bit5=Ausgabetreiber-Freigabe
out2:	mov	DCR,r4		;(2)
	mov	a,ECR_Bits	;(2)
	anl	a,#00000101b	;(2)
	jz	bidi1		;(3) Modus nicht SPP oder AutoStrobe
	clr	direction	;(2) DCR.5 klebt auf 0
bidi1:	LDX	OutC	;Damit die o.g. Hintertür klappt!
	anl	a,#11110000b
	mov	r3,a
	mov	a,r4
	jb	DirectIo,out2ni	;Nicht invertieren
	xrl	a,#00001011b
out2ni:	anl	a,#00001111b
	orl	a,r3
	STX
	jnb	Control5V,DirChanged
	acall	DoOcControl	;OC simulieren
DirChanged:
	jb	DirectIo,out2e	;Nicht die Datenportrichtung beeinflussen!
	acall	DirChanged0	;Ggf. OC-Simulation umschalten
	jnb	Data5V,DoSetOeb	;Ohne OC-Simulation Tristate-Treiber schalten
out2e:	ret
	
;=== OUT auf Adresse +3 (EPP-Adresse) ===
out3:	mov	r3,#0110b	;AddrStrobe (17) LOW
	jmp	out_epp

;=== OUT auf Adresse +4 (EPP-Daten) ===
out4:	mov	r3,#1100b	;DataStrobe (14) LOW
	jmp	out_epp

;=== OUT auf Adresse +400 (ECP-Daten-FIFO) ===
out400:	mov	a,ECR_Bits
	anl	a,#01001100b	;Konfigurationen mit FIFO
	jz	nix
	mov	a,#1		;Daten (=1)
out0ecp:
	jb	fifof,nix	;Nichts tun, wenn FIFO voll!
	mov	r0,fifow
	mov	@r0,AR4		;abspeichern
	inc	r0
	mov	@r0,a
	jmp	IncFifoW

;=== OUT auf Adresse +402 (ECP-Steuerport) ===
out402:	mov	a,r4
	call	SetECR
	jmp	DirChanged2	;Änderung OC-Status Steuerport?

;=== OUT auf Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
out404:	mov	a,r4
	mov	dptr,#BitReverse
	movc	a,@a+dptr
	STX	OEB
	ret

;=== OUT auf Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
out405:	mov	a,r4
	rl	a		;ERR auf richtige Bit-Position
	anl	a,#00010000b
	orl	a,#00100000b	;LED bleibt Ausgabe
	STX	OEA
	LDX	OEC	;Treiber der Steuerbits holen
	anl	a,#00001111b
	mov	r3,a
	mov	a,r4
	cpl	a
	rlc	a	;BSY und ACK vertauschen...
	rl	a
	rrc	a
	rrc	a
	anl	a,#11110000b
	orl	a,r3
	STX
	ret

;=== OUT auf Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
out406:	LDX	OEC	;Treiber der Statusbits holen
	anl	a,#11110000b
	mov	r3,a
	mov	a,r4
	anl	a,#00001111b	;Nur Bits 0..3 benutzen
	orl	a,r3
	STX
sf1:	ret

;=== OUT auf Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
out407:	mov	a,r4
SetFea:	anl	a,#01000101b	;Nur "bekannte" Bits durchlassen
	orl	a,#00000010b	;TPControl stets EIN (keine Pullups vorhanden)
	xch	a,Feature
	xrl	a,Feature	;Veränderungen?
	jz	sf1		;nein, gar nichts tun
	setb	FeatureChanged	;in EEPROM brennen (verzögert)
	jnb	ACC.6,setf6
	LDX	OutC
	xrl	a,#01001011b	;sofort entsprechende Bits drehen!
	STX
setf6:	acall	DirChanged0	;OC-Status Datenport nachführen
	ajmp	DirChanged2	;OC-Status Steuerport nachführen

;=== IN von Adresse +0 (Datenport) ===
in0:	mov	a,ECR_Bits
	anl	a,#10110011b	;0 = eine FIFO-Betriebsart?
	jz	infifo		;Datenport nicht rücklesbar!
	LDX	PinsB
	mov	dptr,#BitReverse
	movc	a,@a+dptr
nix:	ret

;=== IN von Adresse +1 (Statusport) ===
in1:			;(3-4) Aufruf VR: R3!
	LDX	PinsA	;(4)
	orl	a,#0EFh ;(2)	;ERR-Bit invers ausmaskieren
	mov	r3,a	;(1)
	LDX	PinsC	;(4)
	orl	a,#0Fh	;(2)
	rlc	a	;(1)	;ACK ausschieben nach C
	anl	a,r3	;(1)	;ERR ist gerade an passender Bitposition!
	rl	a	;(1)	;BSY nun auf Bit 0, C bleibt
	rrc	a	;(1)	;ACK einschieben, jetzt BSY in C
	jb	DirectIo,in1ni	;(4)
	cpl	c	;(1)	;BSY invertieren
in1ni:	rrc	a	;(1)	;Nun sind Bit 6 und 7 vertauscht!
	anl	a,EppTimeOut	;(2)Bit 0 löschen, wenn kein EPP-TimeOut
	RETNC		;(5) = (31..32)

;=== IN von Adresse +2 (Steuerport) ===
in2:	mov	a,ECR_Bits
	anl	a,#10110011b	;0 = FIFO-Betriebsart
	jz	in2o
	mov	a,DCR
	orl	a,#11001111b	;Interrupt- und Richtungs-Bit
	mov	r3,a
	LDX	PinsC
	jb	DirectIo,in2ni
	xrl	a,#00001011b
in2ni:	orl	a,#11110000b	;Ungenutzte Bits sind stets 1
	anl	a,r3
;NEU: Auch im Standard-Modus ist das Richtungsbit immer 0
	ret
in2o:	;Rücklesen des Ausgaberegisters; erspart Kopfzerbrechen mit ECP
	mov	a,DCR
	orl	a,#11000000b	;obere Bits lesen immer 1
	ret
retff:	mov	a,#0FFh
retin:	ret

;=== IN von Adresse +3 (EPP-Adresse) ===
in3:	mov	r3,#0111b	;mit AddrStrobe (17) LOW
	ajmp	in_epp

;=== IN von Adresse +4 (EPP-Daten) ===
in4:	mov	r3,#1101b	;mt DataStrobe (14) LOW
	ajmp	in_epp

;=== IN von Adresse +400 (ECP-FIFO) ===
in400:	mov	a,ECR_Bits
	anl	a,#11001100b
	jz	retff		;ohne ECP gibts kein Port+400h
	mov	a,#00010000b	;Konfigurationsregister (Konstante) A
	jb	ECR_Bits.7,retin
infifo:	mov	r0,fifor
	mov	a,@r0		;FIFO lesen
	jb	fifoe,retin	;Letztes FIFO-Byte liefern wenn FIFO leer
	mov	r3,a		;Logische Schwäche bei BeyondLogic:
				;Wohin mit dem "gelesenen" PeriphAck?
	acall	IncFifoR
	mov	a,r3
	ret

;=== IN von Adresse +401 (ECP-???) ===
in401:	jnb	ECR_Bits.7,retff
	clr	a		;Konfigurationsregister (Konstante) B
	ret

;=== IN von Adresse +402 (ECP-Steuerport) ===
in402:	mov	a,ECR
	ret

;=== IN von Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
in404:	LDX	OEB
	mov	dptr,#BitReverse
	movc	a,@a+dptr
	ret

;=== IN von Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
in405:	LDX	OEA
	anl	a,#00010000b
	rr	a		;ERR auf richtige Bit-Position
	mov	r3,a
	LDX	OEC
	rlc	a	;BSY und ACK vertauschen...
	rl	a
	rrc	a
	rrc	a
	anl	a,#11110000b	;4 Bits
	orl	a,r3		;und ein fünftes
	ret

;=== IN von Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
in406:	LDX	OEC
	anl	a,#00001111b	;Nur Bits 0..3, übrige Bits sagen "Eingabe"
	ret

;=== IN von Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
in407:	mov	a,Feature
	ret

;=== Warte-Hilfsbefehl, Argument R4 = Wartezeit in 4 µs ===
wait:	inc	r4		;Wir brauchen 4x6=24 Takte pro Durchlauf
w1:	inc	dptr		;kurzer Befehl mit langer Dauer!
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr
	inc	dptr
	djnz	r4,w1		;3 Takte
	ret

;=== Sprungtabelle für IN- und OUT-Befehle ===
;					Abgefangener|EP2 Out EP2 Out|EP2 In
;	=========> USB-Pipe-Belegung:	"ASM-Befehl"|1. Byte 2. Byte|Antwort
;					------------------------------------
upv:	ajmp	out0	;		OUT +0,al   |	00h	al  |	-
	ajmp	out1	;		OUT +1,al	01h	al	-
	ajmp	out2	;		OUT +2,al	02h	al	-
	ajmp	out3	;EPP-Adresse	OUT +3,al	03h	al	-
	ajmp	out4	;EPP-Daten	OUT +4,al	04h	al	-
	ajmp	out4	;		OUT +5,al	05h	al	-
	ajmp	out4	;		OUT +6,al	06h	al	-
	ajmp	out4	;		OUT +7,al	07h	al	-
	ajmp	out400	;nur ECP	OUT +400h,al	08h	al	-
	ajmp	nix	;		OUT +401h,al	09h	al	-
	ajmp	out402	;		OUT +402h,al	0Ah	al	-
	ajmp	nix	;		OUT +403h,al	0Bh	al	-
	ajmp	out404	;HINTERTÜR	OUT +404h,al	0Ch	al	-
	ajmp	out405	;HINTERTÜR	OUT +405h,al	0Dh	al	-
	ajmp	out406	;HINTERTÜR	OUT +406h,al	0Eh	al	-
	ajmp	out407	;Feature	OUT +407h,al	0Fh	al	-
	ajmp	in0	;		al = IN +0	10h	-	al
	ajmp	in1	;		al = IN +1	11h	-	al
	ajmp	in2	;		al = IN +2	12h	-	al
	ajmp	in3	;EPP-Adresse	al = IN +3	13h	-	al
	ajmp	in4	;EPP-Daten	al = IN +4	14h	-	al
	ajmp	in4	;		al = IN +5	15h	-	al
	ajmp	in4	;		al = IN +6	16h	-	al
	ajmp	in4	;		al = IN +7	17h	-	al
	ajmp	in400	;nur ECP	al = IN +400h	18h	-	al
	ajmp	in401	;		al = IN +401h	19h	-	al
	ajmp	in402	;		al = IN +402h	1Ah	-	al
	ajmp	retff	;		al = IN +403h	1Bh	-	al
	ajmp	in404	;HINTERTÜR	al = IN +404h	1Ch	-	al
	ajmp	in405	;HINTERTÜR	al = IN +405h	1Dh	-	al
	ajmp	in406	;HINTERTÜR	al = IN +406h	1Eh	-	al
	ajmp	in407	;Feature	al = IN +407h	1Fh	-	al
	ajmp	wait	;Code zum Warten (kein)		20h   x * 4 µs	-
;					------------------------------------
;Viele OUT-Befehle tun nichts, fast genauso viele IN-Befehle liefern nur FFh.
;Die Warte-Routine bietet die Möglichkeit, Port-Zugriffe zeitlich
;auseinanderzuziehen. (Hoffentlich braucht das niemand!)
;Die Bulk-Daten dürfen beliebig viele OUT- und IN-Befehle zusammenfassen.
;Alle nicht gelisteten 1. Bytes (also >20h) sowie fehlende Folge-Bytes
;im jeweiligen Bulk-Datenblock führen zum Abbruch der Bearbeitung des Blocks.

Aufruf:			;R3=UP-Nummer
	mov	a,r3
	JMPTBL	upv

;=== IEEE1284 ===
Request:
;Datenbyte liegt bereits an PortB, Steuerleitungen im Defaultzustand
	clr	c
	STX	OutC,1101b	;SEL=high, AF=low
	mov	r2,#7		;max. 10 ms warten
	mov	r4,#0
rl1:	acall	in1		;(27)
	anl	a,#01111000b	;(2)	ACK=LOW, PE=HIGH, SEL=HIGH, ERR=HIGH?
	xrl	a,#00111000b	;(2)
	jz	ry1		;(3)
	djnz	r4,rl1		;(3) * 256 = 9216T = 1536µs
	djnz	r2,rl1
	RETC
ry1:	STX	OutC,1100b	;STB=low
	mov	r4,#2		;1 µs
rl3:	djnz	r4,rl3		;(3)
	STX	,1111b		;AF=high, STB=high
	mov	r2,#7		;max. 10 ms warten
	;mov	r4,#0
rl2:	acall	in1		;(27)
	anl	a,#01111000b	;(2)	ACK=HIGH, PE=LOW, SEL=HIGH, ERR=LOW?
	xrl	a,#01010000b	;(2)
	jz	ry2		;(3)
	djnz	r4,rl2		;(3) * 256 = 9216T = 1536µs
	djnz	r2,rl2
	setb	c
ry2:	ret	
	
Nibble:
;PA: Nibble in A (sowie R5) im High-Teil!
;VR: R0,R2,R3
	clr	c
	STX	OutC,1101b	;AF low
	mov	r3,#3		;max. 1 ms warten
	mov	r2,#0
	mov	r0,#LOW(PinsC)
nl1:	LDX			;(2)
	jnb	ACC.ACK,ny1	;(4)
	djnz	r2,nl1		;(3) * 256 = 2304T = 400µs
	djnz	r3,nl1
	RETC
ny1:	rl	a		;Diese Schaltung ist nibble-mode-freundlich!
	anl	a,#11100000b	;Bits schon richtig
	mov	r2,a
	LDX	PinsA
	anl	a,#00010000b
	orl	a,r2
	push	ACC
	 STX	OutC,1111b	;AF high
	 mov	r3,#3		;max. 1 ms warten
	 mov	r2,#0
	 mov	r0,#LOW(PinsC)
nl2:	 LDX			;(2)
	 jb	ACC.ACK,ny2	;(4)
	 djnz	r2,nl2		;(3) * 256 = 2304T = 400µs
	 djnz	r3,nl2
	 setb	c		;TimeOut
ny2:	pop	ACC
	ret

TwoNibbles:
;VR: R0,R2,R3,R5
	acall	Nibble	;Low-Nibble
	jc	tne
	swap	a
	mov	r5,a
	acall	Nibble	;High-Nibble
	orl	a,r5
tne:	ret

GetDeviceId:
;Beschafft Drucker-ID im Nibble-Modus
;PA: CY=1: Fehler
;    CY=0: Daten über EP0 (64-Byte-weise) übertragen, kein STALL
;VR: R0..R7
	STX	OutC,0111b	;SEL=low, INI=high, AF=high, STB=high
	STX	PREV,20h	;"Get Device ID using Nibble Mode" (bitrevers)
	acall	Request
	jc	gdie
	mov	dptr,#GetDevId	;Kernfunktion für Universalroutine
	mov	r4,#11000000b	;"Vendor-Request", IN
	call	LongEP0		;Universalroutine arbeiten lassen
	STX	OutC,0111b	;Standard-Modus zurück
gdie:	ret
	
GetDevId:
;Callback-Routine mit R4=Transferlänge und R1=AutoDat
	jb	F0,GetDevIdCont
	acall	TwoNibbles
	jc	gdie
	STX1
	djnz	r4,gdi1
	ret
gdi1:	mov	DPH0,a	;High-Teil retten
	acall	TwoNibbles
	jc	gdie
	STX1
	;DPTR := 2 - Länge (also im Regelfall eine negative 16-bit-Zahl)
	mov	r5,a
	mov	a,#2
	subb	a,r5
	mov	DPL0,a	;Low-Teil
	clr	a
	subb	a,DPH0
	mov	DPH0,a	;High-Teil
	clr	c
	setb	F0	;Fortsetzungs-Bit
	sjmp	gdi3
GetDevIdCont:
	mov	a,DPH0
	jz	gdi4	;Rest mit Nullen füllen
	acall	TwoNibbles
	jc	gdie
	inc	dptr	;solange negativ kommen Bytes vom Drucker
gdi4:	STX1
gdi3:	djnz	r4,GetDevIdCont
	ret

;== Drucker-Klassen-Simulation ==
UniXfer:	;Aufruf wenn Daten in OUT4BUF vorhanden sind
	LDX	PinsC
	jb	ACC.BSY,ux1	;BUSY prüfen, nichts tun wenn beschäftigt
	mov	a,UniIdx	;Index in OUT4BC holen
	add	a,#LOW(OUT4BUF)
	mov	DPL0,a
	mov	DPH0,#HIGH(OUT4BUF)
	movx	a,@dptr		;Datenbyte aus Puffer holen
	acall	out0a		;anlegen
	mov	a,#0111b	;SEL=low für Standard-Parallelport-Betrieb
	STX	OutC
	clr	ACC.STB
	STX			;Strobe-Signal erzeugen (STB=low)
	inc	UniIdx		;Index erhöhen
	setb	ACC.STB
	STX			;6 Zyklen später (1 µs) STB=HIGH
	mov	a,#166
	acall	StartLed
	LDX	OUT4BC
	xrl	a,UniIdx	;Puffer ausgelesen?
	jnz	ux1		;nein
	STX			;ja, Puffer an USB übergeben, scharf machen
	mov	UniIdx,a	;Index auf Null stellen
ux1:	ret

;=== LED-Spielerei ===

StartLed:
;Start des LED-Blinkens mit a=Blinkperiodendauer, 0=Maximum
;Blinkt die LED bereits, wird nur die künftige Blinkperiode gesetzt
;VR: A,R0=LOW(OUTA)
	mov	LedTime,a	;Zeitzähler (Monoflop) neu starten
	mov	BlinkF,a	;Blinkfrequenz setzen
	jb	LedBlink,nost
	mov	Blink,a
	setb	LedBlink
	LDX	OutA
	clr	ACC.LED
	STX	OutA		;LED ausschalten (Zugriff beginnt)
nost:	ret

HandleUsbSleep:
;Alles auf Energiesparen schalten; kehrt mit dem Wakeup des Prozessors zurück
	LDX	OEA
	push	ACC
	STX	,0		;alles hochohmig und Energie sparend
	LDX	NEXT		;OEB
	push	ACC
	STX	,0
	LDX	NEXT		;OEC - mit der Hintertür als Eingang möglich
	push	ACC
	STX	,0
	 mov	PCON,#31h	;Tiefschlaf hier
	pop	ACC
	STX			;OEC
	pop	ACC
	STX	PREV		;OEB
	pop	ACC
	STX	PREV		;OEA
	ret
	
SaveFeature:
	clr	FeatureChanged
	mov	a,Feature
	STORX	Scratch
	call	EReqInitBoot
	STXW1	AutoPtr,Scratch
	inc	r1		;XAutoDat
	mov	r4,#1
	mov	dptr,#0FFFBh	;Letztes Byte vor Seriennummer
	jmp	EWrite		;Daten schreiben (ohne Verify)

;=====================
;== Initialisierung ==
;=====================
main:
	clr	a
	mov	CKCON,a		;Handbremse für "movx" lösen
	mov	r0,#7Fh
	mov	MPAGE,r0	;die meisten XRAM-Register
	mov	SP,r0
clr_ram:mov	@r0,a
	djnz	r0,clr_ram
	call	MakeBitReverse
;Persistente Konfiguration lesen und übernehmen
	call	EReqInitBoot
	STXW1	AutoPtr,Scratch
	inc	r1		;XAutoDat
	mov	r4,#1
	mov	dptr,#0FFFBh	;Letztes BYTE vor Seriennummer
	call	ERead		;persistente Daten lesen
	LOADX	Scratch
	inc	a
	jz	w3		;FFh als 00h annehmen
	dec	a
w3:	call	SetFea		;setzen
	clr	FeatureChanged
;Ports initialisieren
	mov	a,#20h		;PS/2-Modus (zweckmäßiger?)
	call	SetECR

	STX	CPUCS,0		;Wir brauchen keinen CLK24-Ausgang

	STX	OutA,20h	;LED einschalten (bleibt an, inverse Op.)
	STX	NEXT,0		;0 gibt das BIOS standardmäßig aus (?)
	STX	NEXT,07h	;AF, INI und STB auf High, SEL auf Low

	STX	OEA, 20h	;Ausgabe für LED auf PortA
	jb	DirectIo,w4	;Bei DirectIo bleibt alles Eingabeports
	STX	NEXT,0FFh	;alles Ausgabe auf PortB
	STX	NEXT,0Fh	;Ausgabe PortC Bit 0..3
w4:
	setb	EICON.5		;Resume-Interrupt (auch bei EA=0)

	STX	USBPAIR,011011b	;OUT4,OUT2,IN4,IN2 doppelt gepuffert
				;(macht der EZUSB beinahe gratis!)
	STX	NEXT,00010101b	;nur EP4 und EP2 ist OK
	STX	NEXT
	STX	NEXT,0		;keine isochronen Transfers
	STX	NEXT
	LDX	USBCS
IF Always_Renum=0
	jb	ACC.1,mainloop	;ReNum überspringen, falls vom EEPROM geladen
	clr	ACC.2		;DiscOE
	STX
ENDIF
	setb	ACC.3		;DisCon
	STX
	setb	ACC.1		;ReNum
	STX
	mov	r5,#6		;1,5 Sekunden warten
	mov	r3,#0
w2:	acall	w1		;verputzt R4=0
	djnz	r3,w2
	djnz	r5,w2
	xch	a,r4		;A=0
	dec	a		;A=0FFh
	STX	IN07IRQ		;alle alten Interruptanforderungen löschen
	STX	NEXT		;OUT07IRQ
	STX	NEXT		;USBIRQ
	xch	a,r4
	clr	ACC.3		;DisCon
	STX	USBCS
	setb	ACC.2		;DiscOE
	STX
;===================
;== Hauptschleife ==
;===================
mainloop:
	call	usr36		;Zyklischer User-Aufruf
	jnb	FeatureChanged,skipSF
	call	SaveFeature
;Zuerst "USB-Interrupt-Polling"
skipSF:	jnb	ECR_Bits.2,skip0;SPP-FIFO-Transfer prüfen
	call	SppXfer
skip0:	jnb	ECR_Bits.3,skip1;ECP-FIFO-Transfer prüfen
	acall	EcpXfer
skip1:	LDX	USBIRQ		;USB-Interrupts prüfen
	jz	nu4		;Kurzschluss-Sprung
	STX			;alle Anforderungen löschen
	mov	IntReq,a
	jnb	IntReq.0,nu0	;SUDAV-Anforderung?
	clr	a		;lang (Blinken mit 2 Hz)
	acall	StartLed
	call	HandleSUD	;liefert CY=1 für EP0Stall
	mov	a,#1		;HSNAK
	rlc	a		;EP0Stall
	STX	EP0CS
nu0:
	jnb	IntReq.1,nu1	;SOF-Anforderung?
	jnb	LedBlink,nu1	;LED blinkt?
	LDX	OutA
	djnz	Blink,nbl
	cpl	ACC.LED
	STX
	mov	Blink,BlinkF
nbl:	djnz	LedTime,nu1
	setb	ACC.LED
	STX			;Nach paar Millisekunden bleibt das Licht an
	clr	LedBlink
nu1:
	jnb	IntReq.3,nu3	;Einschlaf-Anforderung?
	;Irgendwie kommt es beim Busaufzählen zu 2 Einschlaf-Aufforderungen,
	;die zum "Defekt" des USB-Gerätes führen.
	;Ob das Abwarten von SET CONFIURATION wirklich ausreicht? Ja!
	jnb	Configuration.0,nu3
	call	HandleUsbSleep
nu3:
	jnb	IntReq.4,nu4	;USB-Reset?
	;ohne Behandlung folgt Absturz beim Booten mit angestecktem Gerät?
	mov	Configuration,#0
	call	ResTog2		;etwa wie SetInterface (=SetAltSetting)
	call	ResTog4
nu4:
	LDX	OUT4CS
	jb	ACC.1,nep4
	acall	UniXfer
nep4:
;dann Auswertung des OUT-Bulk-Transfers
;AutoPtr: OUT2BUF-Zeiger, r2=Länge
;7E:R1: IN2BUF-Zeiger, Länge ergibt sich am Ende durch R1
;R3=Befehlskode
;R4=Daten (bei IN-Befehl A)
	LDX	OUT2CS
	jb	ACC.1,mainloop
	LDX	NEXT
	mov	r2,a		;Anzahl der Bytes des Bulk-Transfers

	mov	a,#100		;kurz (Blinken mit 5 Hz)
	acall	StartLed

	STX	AUTOPTRH,HIGH(OUT2BUF)
	STX	NEXT,LOW(OUT2BUF)
	mov	r1,#LOW(IN2BUF)

l1:	jnb	ECR_Bits.2,skip2;SPP-FIFO-Transfer prüfen
	call	SppXfer
skip2:	jnb	ECR_Bits.3,skip3;ECP-FIFO-Transfer prüfen
	acall	EcpXfer
skip3:	LDX	AUTODATA	;"Befehls"-Byte vom OUT2-Puffer lesen
	CMP	a,#21h
	jc	l2
	call	usr2f		;zur Extension springen
	jc	raus		;Bulk-Block-Rest bei Fehler verwerfen
	sjmp	bo1
l2:
	mov	r3,a
	jb	ACC.4,InBefehl
	dec	r2
	mov	a,r2
	jz	raus		;Fehler, wenn kein Folge-Byte folgt
	LDX			;weiteres AUTODATA-Byte lesen
	mov	r4,a
	acall	Aufruf
	sjmp	bo1
InBefehl:
	LDX	IN2CS		;kann (=will) Befehl nicht verarbeiten,
	jb	ACC.1,raus	;wenn Daten noch nicht abgeholt wurden
	acall	Aufruf
	dec	MPAGE
	movx	@r1,a		;IN-Byte in IN2BUF ablegen
	inc	MPAGE
	inc	r1
bo1:	djnz	r2,l1
raus:
	mov	a,r1
	;add	a,#-LOW(IN2BUF)	;ist glücklicherweise Null
	jz	no_in
	STX	IN2BC		;Bulk-In "anschubsen"
no_in:	STX	OUT2BC		;Bulk-Out scharfmachen
	jmp	mainloop

;USB-Deskriptoren
;********************************************
;** Antwort auf Get Descriptor: Device (1) **
;********************************************
;Geräte-Beschreiber
DeviceDescriptor:
	db	18	;bLength
	db	1	;bDescriptorType, 1=DEVICE
	DWI	101h	;bcdUSB 1.0
	db	0	;bDeviceClass		Multifunktion
	db	0	;bDeviceSubClass
	db	0	;bDeviceProtocol
	db	64	;bMaxPacketSize0
IF MyID
	DWI	16C0h	;idVendor		Voti
	DWI	06B3h	;idProduct		h#s Sub-ID
	DWI	461Fh+Interfaces ;bcdDevice	USB2LPT, USB2LPT2
ELSE
	DWI	0547h	;idVendor		Cypress
	DWI	2131h	;idProduct		EZ-USB (1!)
	DWI	4621h	;bcdDevice		irgendetwas
ENDIF
	db	1	;iManufacturer (1)
	db	2	;iProduct (2)
	db	0	;iSerialNumber		keine Seriennummer!
	db	1	;bNumConfigurations

;********************************************
;** Antwort auf Get Descriptor: Config (2) **
;********************************************
ConfigDescriptor:
;Konfigurations-Beschreiber 0
	db	9	;bLength
	db	2	;bDescriptorType	2=CONFIG
	DWI	CfgE-ConfigDescriptor	;wTotalLength
	db	Interfaces	;bNumInterfaces
	db	1	;bConfigurationValue (willkürliche Nummer dieser K.)
	db	3	;iConfiguration (3)
	db	80h	;bmAttributes (Busversorgt, kein Aufwecken)
	db	100/2	;MaxPower (in 2 Milliampere)
;Interface-Beschreiber 0, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	0	;bInterfaceNumber
	db	0	;bAlternateSetting
	db	2	;bNumEndpoints
	db	-1	;bInterfaceClass	hersteller-spezifisch
	db	-1	;bInterfaceSubClass
	db	-1	;bInterfaceProtocol
	db	4	;iInterface (4)
;Enden-Beschreiber C0I0A0:Bulk EP2OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	2	;bEndpointAddress	EP2OUT
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
;Enden-Beschreiber C0I0A0:Bulk EP2IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	82h	;bEndpointAddress	EP2IN
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
IF Interfaces = 2
;Interface-Beschreiber 1, Alternative 0:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	1	;bInterfaceNumber
	db	0	;bAlternateSetting
	db	1	;bNumEndpoints
	db	7	;bInterfaceClass	Drucker
	db	1	;bInterfaceSubClass	Drucker
	db	1	;bInterfaceProtocol	unidirektional
	db	5	;iInterface (5)
;Enden-Beschreiber C0I1A0:Bulk EP4OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	4	;bEndpointAddress	EP4OUT
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
;Interface-Beschreiber 1, Alternative 1:
	db	9	;bLength
	db	4	;bDescriptorType	4=INTERFACE
	db	1	;bInterfaceNumber
	db	1	;bAlternateSetting
	db	2	;bNumEndpoints
	db	7	;bInterfaceClass	Drucker
	db	1	;bInterfaceSubClass	Drucker
	db	2	;bInterfaceProtocol	bidirektional
	db	6	;iInterface (6)
;Enden-Beschreiber C0I1A1:Bulk EP1OUT
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	4	;bEndpointAddress	EP4OUT
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
;Enden-Beschreiber C0I1A1:Bulk EP1IN
	db	7	;bLength
	db	5	;bDescriptorType	5=ENDPOINT
	db	84h	;bEndpointAddress	EP4IN
	db	2	;bmAttributes		2=BULK
	DWI	64	;wMaxPacketSize
	db	0	;bInterval		keine Bedeutung bei BULK
ENDIF
CfgE:
;***=======******************************************
;** Etwaige Antwort auf Get Descriptor: String (3) **
;***=======******************************************
StringDescriptors:
;Wer Umlaute oder Chinesisch mag, muss für die folgenden Strings einen Editor
;benutzen, welcher entweder ISO-Latin1 oder UTF-8 speichert!
	db	0D0h,087h,0		;Deutsch (0407h in UTF8)
	db	"haftmann#software",0
	db	"h#s USB2LPT - der Umsetzer, der wirklich funktioniert!",0
	;db	"h#s USB2LPT - the converter that really works!",0
	db	"Busversorgt, kein Aufwecken",0
	;db	"Power supply by USB wire, no remote wakeup",0
	db	"USB2LPT - direkt steuerbares Parallelport",0
	;db	"USB2LPT - directly controlable parallel port",0
IF Interfaces = 2
	db	"Unidirektionales Drucker-Interface wie PL-2305",0
	;db	"Unidirectional printer interface, like PL-2305",0
	db	"Bidirektionales Drucker-Interface wie PL-2305",0
	;db	"Bidirectional printer interface, like PL-2305",0
ENDIF

ALIGN 256
BitReverse: ds 100h	;Hier Zugriff mit movc a,@a+dptr möglich
StringPuffer: ds 100h	;Dieser Puffer darf Seitengrenze nicht überlappen!

END

;"Richtige" Interrupt-Simulation (ACK) erfordert Schaltungsänderung!
;PortB und PortC müssten getauscht werden, weil nur INT4 oder INT6 geht
;Und ist schwierig am PC zu simulieren (will sagen, ich weiß nicht, wie)
;Ganz zu schweigen von ECP-DMA!
;USB-mäßig wäre es natürlich ein simpler 1-Byte-USB-INT-Transfer...

Detected encoding: UTF-80