;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< 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...