Source file: /~heha/basteln/PC/USB2LPT/usb2lpt.zip/src/firmware/USB2LPT6/usb2lpt6.c

/* Firmware für ATmega8 im USB2LPT 1.6, haftmann#software 11/10
 * basierend auf: »usbdrv« von Objective Development GmbH (Österreich),
 * Beispieldatei von Henning Paul
 * Lizenz: GNU GPL v2 (see License.txt)
 * Zu übersetzen und zu brennen mit zugehörigem »makefile«,
 * bspw. »make« zum Kompilieren, »make flash« zum Brennen (ggf. mit Kompilation),
 * (einmalig pro Chip) »make fuse« zum Setzen der Sicherungen (Taktquelle u.ä.)
 * Das Paket »winavr« ist erforderlich!
 * Alle Schiebeoperationen (>>, <<) sind wegen Compiler-Umständen (int-Cast)
 * in Schiebe-Zuweisungen (>>=, <<=) umgesetzt;
 * dito auch & und | bei Tests (bspw. bei »if«) mit mehr als einem Bit.
 * Das Schlüsselwort »inline« scheint unnötig, gcc hat eigenen Kopf.
 *
 * Mit dem eingesparten Quarz handelt man sich Probleme bei schwankender
 * Speisespannung ein! Also: Hiermit nur geringe Lasten treiben!
 * An manchen PCs ist die 5V so versaut, dass USB2LPT 1.6 nicht funktioniert.
 * (So die Vermutung!)
 *
*101128	Mittels Option "-nostartfiles" wird Startup-Code minimiert.
	4 KByte (für ATmega48) bleiben illusorisch.
+101213	HID-Interface. Wegen Win64 und Treiberproblem.
-110917	HID-Reportdeskriptor mit Usages versehen
	(damit der Windows HID-Parser funktioniert)
*120301	Problem erkannt: Vista/7 "USB-Verbundgerät" (usbccgp.sys) lädt nicht
	Lösung: Vertauschen der Alternate Settings.
	Evtl. Problem: Treiber geht nicht zu laden??
	Treiber sollte angepasst werden
	Noch ein Problem: Vista/7 lädt usb2lpt.sys auf das Verbundgerät,
	für Nicht-Verbundgeräte ist eine andere PID erforderlich!
+120308	Voreinstellbares ECR via EEPROM-Speicherstelle 0xFFF9
	(gemeinsam mit High-Speed)
	Zusammensetzbare Feature-Requests
+130501	Weitere Mikrocodes für Bitmanipulation (wegen DLPORTIO.DLL):
	0x21 = Bit setzen, 0x22 = Bit löschen, 0x23 = Bit kippen
	Folgebyte Bit 2:0 = Bitnummer, Bit 3 = Echt-Outport-Bit,
	Bit 7:4 = LPT-Adressoffset
 */

// Aus »winavr«
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <string.h>	// memcpy
#include <util/delay.h>	// _delay_us

// Aus »usbdrv«
#include "usbdrv.c"

// Firmware-Datum (Meldung bei A3-Request mit wData==6 und wLength==2)
#define DATEYEAR	2013
#define DATEMONTH	5
#define DATEDAY		28

/************************
 * Hardware:
 *
 * PortB = (zumeist) Steuerport
 *[12] ICP   0 = USB D- (sowie Interrupt und Pullup 10k nach 5P)
 *[13] OC1   1 = USB D+
 *[14] SS    2 = /STB - /C0 (1)
 *[15] MOSI  3 = /AF  - /C1 (14)
 *[16] MISO  4 = /INI -  C2 (16)
 *[17] SCK   5 = /SEL - /C3 (17)
 * [8] XTAL2 6 = Quarz oder frei (wird per internem Pullup high gezogen...)
 * [7] XTAL1 7 = Quarz oder frei (...um erhöhter Stromaufnahme vorzubeugen)
 * Schade, jetzt hätte man ein zweites 8-bit-Port wie beim USB2LPT 1.4 und 1.7
 * herausführen können (Verzicht auf LED /oder/ RESET).
 * Aber die 500 Leiterplatten sind gefertigt...
 *
 * PortC = (zumeist) Statusport
 *[23] ADC0  0 = /LED (low-aktiver Ausgang)
 *[24] ADC1  1 = /ERR -  S3 (15)
 *[25] ADC2  2 =  ONL -  S4 (13)
 *[26] ADC3  3 =  PE  -  S5 (12)
 *[27] ADC4  4 = /ACK -  S6 (10)
 *[28] ADC5  5 =  BSY - /S7 (11)
 *[29] RESET 6 = /Reset (kein E/A-Anschluss) - Lötbrücke nach ONL S4 (13)
 *
 * PortD = Datenport
 *[30] RxD   0 = D0 (2)
 *[31] TxD   1 = D1 (3)
 *[32] INT0  2 = D2 (4)
 * [1] INT1  3 = D3 (5)
 * [2] T0    4 = D4 (6)
 * [9] T1    5 = D5 (7)
 *[10] AIN0  6 = D6 (8)
 *[11] AIN1  7 = D7 (9)
 *
 * In eckigen Klammern: ATmega8-Pinnummer (TQFP-Gehäuse)
 * In runden Klammern: Pinnummer SubD25-Buchse
 * Übrige Pins:
 *[4][6][18] - Betriebsspannung (5V, nicht 3,3V, wegen Ausgabe auf SubD)
 *[3][5][21] - Masse
 *[19][20][22] - zusätzliche A/D-Wandler-Eingänge; A/D-Referenzspannung
 *(18)..(24) - Masse
 *(25) - Masse oder umlötbar 5V
 *
 * Die Zuordnung zu den Portpins erfolgte nach der Maßgabe,
 * Portadressen nicht aufzuteilen, um bei Ausgaben mit zwei Pegelwechseln
 * diese Pegelwechsel exakt gleichzeitig erscheinen zu lassen.
 * Zur Ausrichtung von Ein-Ausgabedaten genügen Schiebebefehle.
 * Damit war PortD als Datenport zwangsweise festgelegt, und INT0 (INT1)
 * nicht für USB nutzbar.
 ************************/

#define FATDATE (((DATEYEAR)-1980)*512+(DATEMONTH)*32+(DATEDAY))
#define BCDDATE (((DATEYEAR)-2000)/10<<12)+(((DATEYEAR)-2000)%10<<8)+((DATEMONTH)/10<<4)+(DATEMONTH)%10

inline __attribute((naked,section(".vectors"))) void __vectors() {
 asm volatile(
"	ldi	r30,0x5F\n"	// initialize stack
"	ldi	r31,0x04\n"
"	rjmp	main\n"
#ifdef PCINT0_vect
"	rjmp	__vector_3\n"::);	// das FAT-Datum wird simuliert
#else
"	.byte	%0,%1\n"	// hier liegt das FAT-Datum
"	.byte	'h','s'\n"	// 2 ungenutzte Bytes
"	rjmp	__vector_5\n"
	:
	:"M" (FATDATE&0xFF),
	 "M" ((FATDATE>>8)&0xFF)
	);
#endif
}
void __do_copy_data(void) __attribute__((naked));
void __do_copy_data(void) {}	// no non-zero-initialized static data

void __do_clear_bss(void) __attribute__((naked));
void __do_clear_bss(void) {}	// no static data at all
int main(void) __attribute__((noreturn,naked));
 
/* Parallelport */
static uchar data_byte;
static uchar DCR;	// Device Control Register (SPP +2)
#define DIRECTION_INPUT (DCR&0x20)
static uchar ECR;	// Extended Control Register (ECP +402)
static uchar ECR_Bits;	// 1-aus-n-Code der höchsten 3 Bits aus ECR
static uchar EppTimeOut;// Bit0 = 0: kein Timeout aufgetreten
			// Bit2 = 0: Interrupt aufgetreten (zz. ungenutzt)
			// Alle anderen Bits müssen =1 sein!
static struct {
 uchar ecr;
 uchar osccal;
 uchar Feature;		// Bit0 = Offene-Senke-Simulation für Daten (SPP +0)
			// Bit2 = Offene-Senke-Simulation für Steuerport (+2)
			// Bit4 = Seriennummer via USB-Deskriptor
			// Bit6 = DirectIO (keine Invertierungen)
			// Bit7 = Bulk-statt-Interrupt
 unsigned long SerialNumber;
} g;
/* FIFO */
#define FIFOSIZE 16	// muss laut Programmlogik Vielfaches von 2 sein
static unsigned Fifo[FIFOSIZE] __attribute((section(".noinit")));
				//wegen ECP brauchen wir 9 bit Breite
static uchar fifor, fifow;	//Indizes

/* sonstiges */
static uchar Led_T;	// "Nachblinkzeit" der LED in ms, 0 = LED blinkt nicht
static uchar Led_F;	// "Blinkfrequenz" in ms (halbe Periode)
static uchar Led_C;	// Blink-Zähler (läuft alles über SOF-Impuls)

// Ein WORD in FIFO schreiben - nichts tun wenn FIFO voll
static void PutFifo(unsigned w) {
 uchar idx, ecr = ECR;		// mit Registern (nicht RAM) arbeiten
 if (ecr & 2) return;		// nichts tun wenn FIFO voll
 idx = fifow;			// in Register holen
 Fifo[idx++] = w;		// einschreiben
 idx &= FIFOSIZE - 1;		// if (idx >= FIFOSIZE) idx = 0;
 ecr &= ~1;			// FIFO nicht leer
 if (idx == fifor) ecr |= 2;	// FIFO voll
 fifow = idx;			// Register rückspeichern
 ECR = ecr;
}

// ein WORD aus FIFO lesen - letztes WORD liefern wenn FIFO leer
// (Ein echtes ECP tut das auch!)
static unsigned GetFifo(void) {
 uchar idx = fifor, ecr = ECR;	// mit Registern (nicht RAM) arbeiten
 unsigned w;
 if (ecr & 1) {			// bei leerer FIFO vorhergehendes WORD liefern
  idx--;
  idx &= FIFOSIZE - 1;		// if (idx >= FIFOSIZE) idx = FIFOSIZE - 1;
  w = Fifo[idx];
 }else{
  w = Fifo[idx++];
  idx &= FIFOSIZE - 1;		// if (idx >= FIFOSIZE) idx = 0;
  ecr &= ~2;			// FIFO nicht voll
  if (fifow == idx) ecr |= 1;	// FIFO leer
  fifor = idx;			// Register rückspeichern
  ECR = ecr;
 }
 return w;
}

// Datenrichtungswechsel von DCR Bit 5 wirksam werden lassen,
// dabei Simulation der Offenen Senke (Open Collector) beachten
static void LptSetDataDir(void) {
 if (DIRECTION_INPUT) {
  DDRD  = 0x00;		// alles EINGÄNGE
  PORTD = 0xFF;		// alle Pull-Ups EIN
 }else{
  PORTD = data_byte;	// Datenbyte ausgeben
  DDRD  = g.Feature & 0x01 ? ~PORTD : 0xFF;
 }
}

// Aktivieren der Parallelschnittstelle bei USB-Aktivität
// oder Rückstellen beim Löschen des DirectIO-Bits im Feature-Register
static void LptOn(void) {
 uchar t;
 LptSetDataDir();	// Datenport: Ausgänge (oder nur Pullups)
 DDRC  = 0x01;		// Statusport: nur die LED ist Ausgang
 PORTC = 0x3E;		// Statusport: fünf Pullups
 t = DCR;
 t ^= 0x0B;
 t <<= 2;
 PORTB = t | 0xC0;
 DDRB  = 0x3C;		// USB-Anschlüsse bleiben Eingänge
}

// <strobe>-Low-Bits auf Steuerport ausgeben und max. 10 µs auf WAIT=H warten
// Liefert Daten eines READ-Zyklus'
// Ausgabedaten müssen vorher auf PORTD gelegt werden.
// Bei Fehler wird das TimeOut-Bit gesetzt
// (Gelöscht wird es durch Schreiben einer Null aufs Statusport.)
static uchar epp_io(uchar strobe) {
 uchar i, saveoe = DDRD, save = PORTB;
 if (PINC & 0x20) goto n_ok;	// BUSY
 PORTB = save & strobe;	// ASTROBE bzw. DSTROBE sowie ggf. WRITE auf LOW
 if (!(strobe & 0x04)) DDRD = 0xFF;	// Ausgabe (jetzt erst)
 i = 24; do{
  if (PINC & 0x20) goto okay;	// sbic PINC,5; rjmp okay (2)
 }while (--i);			// dec r18; brne l1 (3)
n_ok:
 EppTimeOut |= 0x01;		// TimeOut markieren
okay:
 i = PIND;			// Daten einlesen (nur für INPUT relevant:-)
 if (!(strobe & 0x04)) DDRD = saveoe;
 PORTB = save;
 return i;
}

// === OUT auf Adresse +0 (Datenport) ===
static void out0(uchar b) {
 uchar t = ECR_Bits & 0x4C;	// Compiler macht int-Murks ohne Hilfsvariable
 if (t) PutFifo(b);		// eine FIFO-Betriebsart
 else{
  data_byte = b;
  if (!DIRECTION_INPUT) {
   PORTD = b;
   if (g.Feature & 1) DDRD = ~b;	// Simulation OC (mit Pullups)
  }
 }
}

// === OUT auf Adresse +1 (Statusport) ===
// Eine Ausgabe erfolgt nur bei DirectIO = 1 oder bei (via out405)
// auf Ausgabe geschalteten Leitungen.
// Ansonsten keine Ausgabe; Pullups bleiben eingeschaltet
static void out1(uchar b) {
 if (ECR_Bits & 0x10 && !(b & 0x01)) EppTimeOut &=~ 0x01;	// TimeOut-Bit
 b >>= 2;
 if (!(g.Feature & 0x40)) {
  b ^= 0x20;		// BSY-Bit invertieren
  b |= ~DDRC;		// Ausgabe nur bei DirectIO oder vorhandenen Ausgabepins
 }
 b &= 0x3E;
 PORTC = (PORTC & 1) | b;
}

// === OUT auf Adresse +2 (Steuerport) ===
// Bit4=IRQ-Freigabe (nicht unterstützt, aber gespeichert)
// Bit5=Ausgabetreiber-Freigabe
static void out2(uchar b) {
 uchar t = ECR_Bits & 0x05;
 b |= 0xC0;
 if (t) b &=~ 0x20;	// Richtung auf Ausgabe fixieren
 t = b^DCR;	// geänderte Bits
 DCR = b;
 if (!(g.Feature & 0x40)) {	// kein DirectIO?
  if (t & 0x20) LptSetDataDir();// bei Richtungswechsel
  b ^= 0x0B;			// Invertierungen anpassen
 }
 b <<= 2;
 PORTB = b | 0xC0;
 if (g.Feature & 0x04) DDRB = ~b & 0x3C;	// USB-DDR-Bits müssen 0 bleiben
}

// === OUT auf Adresse +3 (EPP-Adresse) ===
static void out3(uchar b) {
 PORTD = b;
 epp_io(~0x24);			// AddrStrobe und Write LOW (0xx0)
}

// === OUT auf Adresse +4 (EPP-Daten) ===
static void out4(uchar b) {
 PORTD = b;
 epp_io(~0x0C);			// DataStrobe und Write LOW (xx00)
}

// === OUT auf Adresse +400 (ECP-Daten-FIFO) ===
static void out400(uchar b) {
 uchar t = ECR_Bits & 0x4C;
 if (t) PutFifo(b | 0x100);	// Datenbyte einschreiben
}

// === OUT auf Adresse +402 (ECP-Steuerport) ===
static void out402(uchar b) {	// svw. SetECR
 uchar t;
 ECR = (b & 0xF8) | 0x05;	// FIFOs leeren
 b >>= 5;
 t = 1;
 t <<= b;
 ECR_Bits = t;			// 1-aus-n-Kode setzen
 EppTimeOut = t & 0x10 ? 0xFE : 0xFF;	// Timeout-Bit voreinstellen
 if (t & 0x05) {		// SPP oder SPP-FIFO?
  if (DIRECTION_INPUT) {
   DCR &=~ 0x20;		// Richtung fest auf AUSGABE
   LptSetDataDir();
  }
 }
 fifor = fifow = 0;
}

// === OUT auf Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
static void out404(uchar b) {
 DDRD = b;
}

// === OUT auf Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
static void out405(uchar b) {
 b >>= 2;
 b |= 0xC1;
 DDRC = b;			// LED-Ausgang immer EIN
 if (!(g.Feature & 0x40)) {	// ohne DirectIO Pullups sicherstellen!
  PORTC |= ~b;
 }
}

// === OUT auf Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
static void out406(uchar b) {
 b <<= 2;
 DDRB = b & 0x3C;		// USB-Anschlüsse stets Eingang
}

// === OUT auf Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
static void out407(uchar b) {
 uchar t;
 b &= 0xD5;			// nur unterstützte Bits durchlassen
 t = g.Feature ^ b;
 g.Feature = b;
 if (t) {
// BrennFeature()
  if (t & 0x01 && !(b & 0x40) && !DIRECTION_INPUT) {
   DDRD = b & 0x01 ? ~PORTD : 0xFF;
  }
  if (t & 0x04 && !(b & 0x40)) {
   DDRB = b & 0x04 ? ~PORTB & 0x3C : 0x3C;
  }
  if (t & 0x40 && !(b & 0x40)) {	// Portrichtungen wiederherstellen
   LptOn();
  }
 }
}
 
// Der Teufel hat ECP erfunden! Wie soll festgestellt werden, ob in der
// FIFO ein Adress- oder ein Datenbyte liegt?
// Mein "echtes" EC-Port ignoriert beim Rücklesen einfach das neunte Bit.
// === IN von Adresse +0 (Datenport) ===
static uchar in0(void) {
 uchar t = ECR_Bits & 0x4C;
 if (t) t = GetFifo();
 else t = PIND;			// ansonsten stets Portpins lesen
 return t;
}

// === IN von Adresse +1 (Statusport) ===
static uchar in1(void) {
 uchar t = PINC;
 t <<= 2;
 t |= 0x07;
 if (!(g.Feature & 0x40)) t ^= 0x80;
 t &= EppTimeOut;
 return t;
}

// b==0: Echtes PIN-Rüclesen, sonst PORT-Rücklesen
static uchar internal_in2(uchar b) {
 b = b ? PORTB : PINB;
 b >>= 2;
 b &= 0x0F;
 if (!(g.Feature & 0x40)) b ^= 0x0B;
 b |= DCR & 0xF0;
 return b;
}

// === IN von Adresse +2 (Steuerport) ===
// Überraschung: Bei einem "genügend neuen" echten Parallelport sind die
// Steuerleitungen gar nicht (mehr) rücklesbar!
// Hier wird diese Einschränkung nur in den 3 FIFO-Betriebsarten nachgeahmt
static uchar in2(void) {
 return internal_in2(ECR_Bits & 0x4C);
}

// === IN von Adresse +3 (EPP-Adresse) ===
static uchar in3(void) {
 return epp_io(~0x20);		// AddrStrobe LOW (0xxx)
}

// === IN von Adresse +4 (EPP-Daten) ===
static uchar in4(void) {
 return epp_io(~0x08);		// DataStrobe LOW (xx0x)
}

// === IN von Adresse +400 (ECP-FIFO) ===
static uchar in400(void) {
 uchar t = ECR_Bits & 0xCC;
 if (!t) return 0xFF;
 if (t & 0x80) return 0x10;	// Konfigurationsregister A (konstant)
 return GetFifo();
}

// === IN von Adresse +401 (ECP-???) ===
static uchar in401(void) {
 return ECR_Bits & 0x80 ? 0 : 0xFF;	// Konfigurationsregister A (0)
}

// === IN von Adresse +402 (ECP-Steuerport) ===
static uchar in402(void) {
 return ECR;
}

// === IN von Adresse +404 (Datenrichtung Datenport) [HINTERTÜR] ===
static uchar in404(void) {
 return DDRD;
}

// === IN von Adresse +405 (Datenrichtung Statusport) [HINTERTÜR] ===
static uchar in405(void) {
 uchar t = DDRC;
 t <<= 2;
 t &= 0xF8;
 return t;
}

// === IN von Adresse +406 (Datenrichtung Steuerport) [HINTERTÜR] ===
static uchar in406(void) {
 uchar t = DDRB;
 t >>= 2;
 t &= 0x0F;
 return t;
}

// === IN von Adresse +407 (USB2LPT-Feature-Register) [HINTERTÜR] ===
static uchar in407(void) {
 return g.Feature;
}

// === WARTEN (blockieren) in 4-µs-Stückelung ===
static void wait(uchar us) {
 us++;
 do{
  _delay_us(4-3E6/F_CPU);	// 4 µs minus 3 CPU-Takte
 }while (--us);		// dec R16; brnz lbl = 3 Takte
}

static uchar getbyte(uchar a) {
 a&=0xF8;
 if (a==0x00) return in0();
 if (a==0x08) return PORTD;
 if (a==0x10) return in1();
 if (a==0x18) {a=PORTC; a<<=2; a|=7; return a;}
 if (a==0x20) return in2();
 if (a==0x28) return internal_in2(1);
 a&=0xF0;
 if (a==0xA0) return in402();
 if (a==0x30) return in3();
 if ((a&0xCF)==0x40) return in4();
 if (a==0x80) return in400();
 if (a==0xC0) return in404();
 if (a==0xD0) return in405();
 if (a==0xE0) return in406();
 if (a==0xF0) return in407();
 return 0xFF;
}

static void setbyte(uchar a, uchar b) {
 a&=0xF0;
 if (a==0x00) out0(b);
 else if (a==0x10) out1(b);
 else if (a==0x20) out2(b);
 else if (a==0xA0) out402(b);
 else if (a==0x30) out3(b);
 else if ((a&0xCF)==0x40) out4(b);
 else if (a==0x80) out400(b);
 else if (a==0xC0) out404(b);
 else if (a==0xD0) out405(b);
 else if (a==0xE0) out406(b);
 else if (a==0xF0) out407(b);
}

static void setbit(uchar a) {
 uchar m = 1;
 m <<= a&7;
 setbyte(a,getbyte(a)|m);
}

static void resbit(uchar a) {
 uchar m = 1;
 m <<= a&7;
 setbyte(a,getbyte(a)&~m);
}

static void cplbit(uchar a) {
 uchar m = 1;
 m <<= a&7;
 setbyte(a,getbyte(a)^m);
}

static void waitbit(uchar a) {
 uchar t=usbSofCount;
 uchar m = 1;
 uchar x = 0;
 m <<= a&7;
 if (a&8) x--;
 while ((getbyte(a)^x)&m) if (t!=usbSofCount) break;
}

// Zyklischer Aufruf; Emulation der SPP-Ausgabefifo
static void SppXfer(void) {
 if (ECR & 1) return;		// bei leerer FIFO nichts tun
 if (PINC & 0x20) return;	//  BSY = H: nichts tun
 PORTD = GetFifo();
 PORTB &= ~0x04;		// /STB = L
 wait(1);
 PORTB |=  0x04;		// /STB = H
}

// Zyklischer Aufruf; Emulation der ECP-Ein/Ausgabefifo
static void EcpXfer(void) {
 unsigned w;
 if (DIRECTION_INPUT) {		// FIFO-Eingabe
  if (ECR & 2) return;		// bei voller FIFO nichts tun
  if (!(PORTB & 0x08)) {	//  /AF = L: Byte einlesen?
   if (PINC & 0x10) return;	// /ACK = H: kein Byte von außen vorhanden
   PORTB |= 0x08;		//  /AF = H setzen
  }				//  /AF = H: hier zweite Handshake-Phase
  if (!(PINC & 0x10)) return;	// /ACK = L: nichts tun!
  w = PIND;
  if (PINC & 0x20) w|=0x0100;	//  BSY = Command(0) / Data(1)
  PutFifo(w);
  PORTB &= ~0x08;		//  /AF = L setzen
 }else{				// FIFO-Ausgabe
  if (PORTB & 0x04) {		// /STB = H: Byte ausgeben?
   uchar t;
   if (ECR & 1) return;		// bei leerer FIFO nichts tun
   if (PINC & 0x20) return;	//  BSY = H: nichts tun!
   w = GetFifo();		// Adress- oder Datenbyte lesen
   PORTD = (uchar)w;		// Byte anlegen
   t = PORTB & 0x34;
   if (w & 0xFF00) t |= 0x08;	// AF setzen oder nicht
   PORTB = t;
   PORTB &= ~0x04;		// /STB = L setzen
  }				// /STB = L: hier zweite Handshake-Phase
  if (!(PINC & 0x20)) return;	//  BSY = L: nichts tun!
  PORTB |= 0x04;		// /STB = H setzen
 }
}

// LED starten mit Blinken, t = Periodendauer
void Led_Start(uchar t) {
 if (!Led_T) {
  Led_C = t;
  PORTC |= 0x01;	// LED ausschalten
 }
 Led_T = Led_F = t;
}

// LED-Zustand alle 1 ms (SOF) aktualisieren
static void Led_On1ms(void) {
 uchar led_t = Led_T, led_c;
 if (!led_t) return;
 led_c = Led_C;		// in Register laden
 if (!--led_c) {
  PORTC ^= 0x01;	// LED umschalten
  led_c = Led_F;	// Zähler neu laden
 }
 if (!--led_t) PORTC &= ~0x01;	// LED einschalten
 Led_T = led_t;		// Register rückschreiben
 Led_C = led_c;
}

static void initIOPorts(void) {
 data_byte = 0x00;
 DCR = 0xCC;
}

// Ausgabedaten bearbeiten und Ergebnisbytes in <buf> aufsammeln,
// liefert Anzahl der nach <buf> geschriebenen Bytes.
// Behandelt auch über Puffergrenzen hinauslaufende 2-Byte-Kommandos
static uchar ProcessInOut(const uchar *data, uchar len, uchar *buf) {
 static uchar PendingByte;
 uchar *InData = buf, command, value;
 if (!len) return 0;
 Led_Start(100);	// schnelles Blinken (5 Hz)
 do{
  command = PendingByte;
  if (command) {
   command &= ~0x10;	// eh' nur 2-Byte-Ausgabekommando
   PendingByte = 0;
   len++;
  }else command = *data++;
  if (command & 0x10) {	// IN-Kommandos
   value = 0xFF;
   if (command==0x10) value = in0();
   else if (command==0x11) value = in1();
   else if (command==0x12) value = in2();
   else if (command==0x13) value = in3();
   else if (command>=0x14
          && command<0x18) value = in4();
   else if (command==0x18) value = in400();
   else if (command==0x19) value = in401();
   else if (command==0x1A) value = in402();
   else if (command==0x1C) value = in404();
   else if (command==0x1D) value = in405();
   else if (command==0x1E) value = in406();
   else if (command==0x1F) value = in407();
   *InData++ = value;	// IN-Kommandos erzeugen Antwort-Bytes
  }else{		// OUT-Kommandos mit Folgebyte
   if (!--len) {	// Wird erst beim nächsten ProcessInOut verarbeitet
    command |= 0x10;
    PendingByte = command;	// merken für Fortsetzung
    break;		// mit len==0 aus Schleife ausbrechen
   }
   value = *data++;
   if      (command==0x00) out0(value);
   else if (command==0x01) out1(value);
   else if (command==0x02) out2(value);
   else if (command==0x0A) out402(value);
   else if (command==0x03) out3(value);
   else if (command>=0x04
          && command<0x07) out4(value);
   else if (command==0x08) out400(value);
   else if (command==0x0C) out404(value);
   else if (command==0x0D) out405(value);
   else if (command==0x0E) out406(value);
   else if (command==0x0F) out407(value);
   else if (command==0x20) wait(value);
   else if (command==0x21) setbit(value);
   else if (command==0x22) resbit(value);
   else if (command==0x23) cplbit(value);
   else if (command==0x24) waitbit(value);
  }
 }while (--len);
 return InData - buf;	// Geschriebene Bytes liefern
}

// Zeiger zum Lesen (nur für gestückelte HID-Feature-Requests) und Schreiben
static uchar ReplyIdxR, ReplyIdxW;
// Antwort-Puffer (nicht überlauf-geschützt!)
// Für alle Arten von Setup-Transfers
static uchar ReplyBuf[128] __attribute((section(".noinit")));

// Häufige Art der Mikrocode-Verarbeitung
static void HidProcessInOut(const uchar*data, uchar len) {
 ReplyIdxW += ProcessInOut(data, len, ReplyBuf+2+ReplyIdxW);
}

static uchar gbRequest;		// Kopie vom letzten SETUP-Paket
static size_t gAdr, gLen __attribute((section(".noinit")));

//-090624: Ohne diese Maßnahme gelangen Daten „in den falschen Hals“
static uchar ExpectEP1OutToken/*=USBPID_DATA0*/;

static uchar replyBuf[128] __attribute((section(".noinit")));

USB_PUBLIC uchar usbFunctionSetup(uchar data[8]) {
 gbRequest = data[1];		// SETUPDAT merken (wie Cypress-Controller)
 gAdr = ((usbRequest_t*)data)->wValue.word;
 gLen = ((usbRequest_t*)data)->wLength.word;

// Rudiment von Henning Paul, für seinen Linux-Treiber
 usbMsgPtr = (usbMsgPtr_t)replyBuf;
// SetFeature -> Stall -> EP1OUT: Data-Toggle rücksetzen
 if (data[0]==0x02 && data[1]==0x03 && data[2]==0x00 && data[4]==0x01) {
  ExpectEP1OutToken=USBPID_DATA0;
 }else if (data[0]==0x21 && data[1]==USBRQ_HID_SET_REPORT && data[4]==0x01) {
  return 0xFF;		// usbFunctionWrite() kümmert sich 
 }else if (data[0]==0xA1 && data[1]==USBRQ_HID_GET_REPORT && data[4]==0x01
   && ReplyIdxW) {
// Die ersten zwei Bytes werden nicht für die Antwortbytes benutzt.
  usbMsgPtr = (usbMsgPtr_t)(ReplyBuf+ReplyIdxR+1);	// Report-ID = Länge, danach Datenbytes
  *(uchar*)usbMsgPtr = data[2];		// gewünschte Report-ID = Länge eintragen
  ReplyIdxR += data[2];
  if (ReplyIdxR>=ReplyIdxW) ReplyIdxR=ReplyIdxW=0;	// alles leer
  return data[6];		// Anzahl Bytes 
 }else if (data[0] == 0xC0) {	// IN, Vendor, Device
// Ehemalige BULK-Transfers
  if (data[1] >= 0x90 && data[1] <= 0x94) {
// bis zu 4 Bytes von SETUPDAT als OUT-Daten verarbeiten
// (Speed! - Für übliche geringe Ausgabemengen kein extra SETUP-Transfer)
   HidProcessInOut(data+2, data[1]-0x90);
   gbRequest = 0xA1;		// vom RAM lesen (lassen)
   gAdr = (size_t)(ReplyBuf+2);	// feste Speicheradresse
   if (gLen > ReplyIdxW) gLen = ReplyIdxW;
   ReplyIdxR=ReplyIdxW=0;	// für's nächste ProcessInOut sei ReplyBuf leer
// Cypress' EZUSB-kompatible Routinen (VendAx.hex)
  }else if (data[1] >= 0xA1 && data[1] <= 0xA3) {	// lesen
#ifdef PCINT0_vect
// im Sonderfall Adresse=6 und Länge=2 wird das Datums-WORD (FAT) geliefert
   if (data[1] == 0xA3 && gAdr == 6 && gLen == 2) {
    ((usbWord_t*)replyBuf)->word = FATDATE;
    return 2;
   }
#endif
   return 0xFF;			// usbFunctionRead() kümmert sich
  }
 }else if (data[0] == 0x40) {	// OUT, Vendor, Device
  if (data[1] >= 0x90 && data[1] <= 0x94) {
// bis zu 4 Bytes von SETUPDAT als OUT-Daten verarbeiten (Bandbreite sparen)
   HidProcessInOut(data+2, data[1]-0x90);
   gbRequest = 0x90;	// alle weiteren OUT-Daten in usbFunctionWrite abarbeiten lassen
  }else if (data[1] == 0xA1 || data[1] == 0xA2) {	// RAM oder EEPROM schreiben
   return 0xFF;		// usbFunctionWrite() kümmert sich
  }
#if 1
 }else	// Ballast!! Welchen Wert hat bmRequestType?
     if(data[1] == 0){       /* ECHO */
        replyBuf[0] = data[2];
        replyBuf[1] = data[3];
        return 2;
    }else if(data[1] == 1){       /* READ_REG */
	if (data[2] == 0){
            replyBuf[0] = in0();
            return 1;
	} 
	else if (data[2] == 1){
            replyBuf[0] = in1();
            return 1;
	}
	else if (data[2] == 2){
	    replyBuf[0] = in2();
            return 1;
	}
    }else if(data[1] == 2){       /* WRITE_REG */
	if (data[2] == 0){
		out0(data[4]);
        }
	else if (data[2] == 2){
		out2(data[4]);
        }
#endif
 }
 return 0;
}

// Datenübertragungsfunktionen für Cypress-kompatible "lange" Setup-Transfers
// sowie für ehemals Bulk-Daten
USB_PUBLIC uchar usbFunctionRead(uchar *data, uchar len) {
 void *adr = (void*)gAdr;	// im Register halten
 if (len > gLen) len = gLen;	// begrenzen auf Restlänge
 if (gbRequest == 0xA1) {	// RAM (oder speziell ReplyBuf) lesen
  memcpy(data, adr, len);
 }else if (gbRequest == 0xA2) {	// EEPROM lesen
  eeprom_read_block(data, adr, len);
 }else if (gbRequest == 0xA3) {	// Flash lesen
  memcpy_P(data, (PGM_P)adr, len);
 }else return 0xFF;		// Fehler: nichts zu liefern! (STALL EP0)
 gAdr = (size_t)adr + len;
 gLen -= len;
 return len;
}

USB_PUBLIC uchar usbFunctionWrite(uchar *data, uchar len) {
 void *adr = (void*)gAdr;	// im Register halten
 if (len > gLen) len = gLen;	// begrenzen auf Restlänge
 if (gbRequest == 0xA1) {	// RAM schreiben
  memcpy(adr, data, len);
  adr += len;
 }else if (gbRequest == 0xA2) {	// EEPROM schreiben
  uchar ll;
  for (ll = 0; ll < len; ll++) {
   eeprom_write_byte(adr, *data++);	// statt eeprom_write_block()
   wdt_reset();			// nach jedem Byte Watchdog beruhigen
   adr++;
  }
 }else if (gbRequest == 0x90) {	// OUT-Daten über EP0 (Vista/Linux-Kompatibilität)
  HidProcessInOut(data, len);
 }else if (gbRequest == USBRQ_HID_SET_REPORT) {
  HidProcessInOut(data+1, data[0]);
 }else return 0xFF;		// Fehler: unerwartete Daten (STALL EP0)
 gAdr = (size_t)adr;
 gLen -= len;
 return len;
}

USB_PUBLIC void usbFunctionWriteOut(uchar *data, uchar len) {
 uchar tmp=ExpectEP1OutToken;
 if (usbCurrentDataToken==tmp) {
  ExpectEP1OutToken=tmp^USBPID_DATA0^USBPID_DATA1;
  uchar InDataLen = ProcessInOut(data, len, ReplyBuf);
  if (InDataLen) usbSetInterrupt(ReplyBuf, InDataLen);	// Antwort zum Host
 }
}

#include <stdlib.h>

#define FETCH_BYTE(src) pgm_read_byte(src)
#define FETCH_BLOCK(d,s,l) memcpy_P(d,s,l);
// Solange der Flash noch Platz hat, kommen die Deskriptoren in den Flash.
// Erst wenn's knapp wird, kommen diese in den EEPROM, wie bei usb2lpt5.
// Den kann aber der (derzeitige) Bootloader nicht ansprechen …

// Generiert USB-String-Deskriptor (mit UTF-16), hier nur 11-Bit-UTF-16
static usbMsgLen_t BuildStringFromUTF8(const char *src) {
 wchar_t *d=(wchar_t*)(replyBuf+2);
 uchar len;
 for (;;) {
  wchar_t c=FETCH_BYTE(src++);
  if (!c) break;
  if (c&1<<7) {
   c=c<<6&0xFFF | FETCH_BYTE(src++)&0x3F;
// if (c&1<<11) c=c<<6|FETCH_BYTE(src++)&0x3F;
  }
  *d++=c;
 }
 len=(uchar)((uchar*)d-replyBuf);
 replyBuf[0]=len;
 replyBuf[1]=USBDESCR_STRING;		// Länge und ID für String-Deskriptor
 return len;
}

// itoa() ist zu fett! Daher diese Ersatzroutine.
static uchar hexDigit(uchar nibble) {
 if (nibble>=10) nibble+=7;
 nibble+='0';
 return nibble;
}

static usbMsgLen_t BuildStringFromSerial() {
 int i;
 uchar *d=replyBuf;
 *d++=18;
 *d++=USBDESCR_STRING;		// Länge und ID für String-Deskriptor
 for (i=3; i>=0; i--) {
  uchar c=((uchar*)&g.SerialNumber)[i];
  *d++=hexDigit(c>>4);
  *d++=0;
  *d++=hexDigit(c&0x0F);
  *d++=0;
 }
 return 18;
}

#define W(x) (x)&0xFF,(x)>>8
#define D(x) W((x)&0xFFFF),W((x)>>16)

PROGMEM const char usbDescriptorDeviceT[] = {
 18,		//bLength
 USBDESCR_DEVICE,        // descriptor type	1
 W(0x0110),	// USB version supported
 0,		// USB_CFG_DEVICE_CLASS,
 0,		// USB_CFG_DEVICE_SUBCLASS,
 0,		// protocol
 8,		// max packet size
 W(0x16c0),	// vendor ID = VOTI, Teilbereich siphec, Teilbereich h#s
 W(0x06B4),	// product ID = USB2LPT mit 2 Interfaces
 W(BCDDATE),	// version (ganze Monate)
 1,		// manufacturer string index
 2,		// product string index
 0,		// serial number string index		PATCH offset 16
 1};		// number of configurations


PROGMEM const char usbDescriptorConfigurationT[57]={
 9,	//bLength
 2,	//bDescriptorType	2=CONFIG
 W(57),	//wTotalLength
 2,	//bNumInterfaces	h#s USB2LPT + HID
 1,	//bConfigurationValue (willkürliche Nummer dieser K.)
 0,	//iConfiguration	ohne Text
 0x80,	//bmAttributes (Busversorgt, kein Aufwecken)
 100/2,	//MaxPower (in 2 Milliampere)	100 mA
//Interface-Beschreiber 0, Alternative 0:
 9,	//bLength
 4,	//bDescriptorType	INTERFACE
 0,	//bInterfaceNumber
 0,	//bAlternateSetting
 2,	//bNumEndpoints
 -1,	//bInterfaceClass	hersteller-spezifisch
 0,	//bInterfaceSubClass	(passt in keine Klasse)
 0,	//bInterfaceProtocol
 0,	//iInterface		ohne Text (TODO wenn Platz)
//Enden-Beschreiber C0I0A1:Interrupt EP1OUT
 7,	//bLength
 5,	//bDescriptorType	ENDPOINT
 1,	//bEndpointAddress	EP1OUT
 3,	//bmAttributes		INTERRUPT	PATCH: Offset 21
 W(8),	//wMaxPacketSize
 10,	//bInterval		10 ms Abfrageintervall (min. zulässig)
//Enden-Beschreiber C0I0A1:Interrupt EP1IN
 7,	//bLength
 5,	//bDescriptorType	ENDPOINT
 0x81,	//bEndpointAddress	EP1IN
 3,	//bmAttributes		INTERRUPT	PATCH: Offset 28
 W(8),	//wMaxPacketSize
 10,	//bInterval		10 ms Abfrageintervall (min. zulässig)
//Interface-Beschreiber 1, Alternative 0:
 9,	//bLength
 4,	//bDescriptorType	INTERFACE
 1,	//bInterfaceNumber
 0,	//bAlternateSetting
 1,	//bNumEndpoints
 3,	//bInterfaceClass	HID
 0,	//bInterfaceSubClass	(ohne Spezifizierung)
 0,	//bInterfaceProtocol
 0,	//iInterface		ohne Text (TODO wenn Platz)
//HID-Beschreiber (an Offset 41)
 9,	// bLength
 0x21,	// bDescriptorType	HID
 W(0x0101),	// BCD representation of HID version
 0,	// target country code
 1,	// number of HID Report Descriptor infos to follow
 0x22,	// descriptor type: report
 W(14+7*9+1),	// total length of report descriptor
//Enden-Beschreiber C0I1A0:Interrupt EP3IN (Dummy)
 7,	//bLength
 5,	//bDescriptorType	ENDPOINT
 0x83,	//bEndpointAddress	EP3IN
 3,	//bmAttributes		INTERRUPT
 W(8),	//wMaxPacketSize
 10,	//bInterval		10 ms Abfrageintervall (min. zulässig)
};

// Für sinnvoll-maximale USB-Performance ist der Report
// nicht größer als die FIFO (8 Bytes) gewählt - inkl. Report-ID
// Die Report-ID gibt die Anzahl der relevanten
// Bytes an, bleiben 1..7 Bytes Nutzdaten
// (bspw. 3 OUT-Befehle und 1 IN-Befehl).
PROGMEM const char usbDescriptorHidReportT[] = {
 0x06, W(0xFF00),	// G Usage Page (Vendor specific)
 0x09, 0x01,		// L Usage (Vendor Usage 1)
 0xA1, 0x01,		// M Collection (Application)
 0x15, 0x00,		// G  Logical Minimum (0)
 0x26, W(0x00FF),	// G  Logical Maximum (255)
 0x75, 0x08,		// G  Report Size (8 bits)

 0x85, 0x01,		// G  Report ID (1)		PATCH: Offset 15
 0x95, 0x01,		// G  Report Count (1 Byte)	PATCH: Offset 17
 0x09, 0x01,		// L  Usage (1)			PATCH: Offset 19
 0xB2, W(0x0102),	// M  Feature (Data,Var,Abs,Buf)
/* lange Reports (für Full Speed und Hi-Speed vorgesehen)
 0x85, 0x3E,		// G  Report ID (62)
 0x95, 0x01,		// G  Report Count (1 Byte)
 0x0B, D(0x0001003B),	// L  Usage (Generic Desktop : Byte Count)
 0xB1, 0x02,		// M  Feature (Data,Var,Abs)
 0x95, 0x3E,		// G  Report Count (62 Byte)
 0x09, 0x3E,		// L  Usage (3E)
 0xB2, W(0x0102),	// M  Feature (Data,Var,Abs,Buf)
 */
 0xC0};			// M End Collection

static uchar BuildHidReportDesc() {
 uchar *d=replyBuf;
 uchar id;
 FETCH_BLOCK(d,usbDescriptorHidReportT,14+9);
 d+=14+9;
 for (id=2; id<8; id++) {
  FETCH_BLOCK(d,usbDescriptorHidReportT+14,9+1);
  d[1]=d[3]=d[5]=id;
  d+=9;
 }
 return 14+7*9+1;
}

USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
 uchar ret=0;
 switch (rq->wValue.bytes[1]) {
  case USBDESCR_DEVICE: {	//1
   FETCH_BLOCK(replyBuf,usbDescriptorDeviceT,sizeof usbDescriptorDeviceT);
   if (g.Feature&0x10 && g.SerialNumber && g.SerialNumber!=0xFFFFFFFF) replyBuf[16]=3;
   ret=sizeof usbDescriptorDeviceT;
  }break;
  case USBDESCR_CONFIG: {	//2
   FETCH_BLOCK(replyBuf,usbDescriptorConfigurationT,sizeof usbDescriptorConfigurationT);
   if (g.Feature&0x80) replyBuf[21]=replyBuf[28]=2;	//BULK
   ret=sizeof usbDescriptorConfigurationT;
  }break;
  case USBDESCR_STRING: switch (rq->wValue.bytes[0]) {	//3
   case 0: ret=BuildStringFromUTF8(PSTR("\xD0\x87\xD0\x89")); break;	// generiert 0x407 und 0x409
   case 1: ret=BuildStringFromUTF8(PSTR("haftmann#software")); break;
   case 2: ret=BuildStringFromUTF8(rq->wIndex.bytes[0]==7
		?PSTR("USB-zu-LPT-Umsetzer, Low-Speed")
		:PSTR("USB2LPT low-speed adapter")); break;
   case 3: ret=BuildStringFromSerial(); break;
  }break;
  case USBDESCR_HID: {		//0x21
   FETCH_BLOCK(replyBuf,usbDescriptorConfigurationT+41,9);
   ret=9;
  }break;
  case USBDESCR_HID_REPORT: {	//0x22
   ret=BuildHidReportDesc();
  }break;
 }
 usbMsgPtr=(usbMsgPtr_t)replyBuf;
 return ret;
}

#if F_CPU == 12800000
volatile uchar timer0Snapshot;
#define TIMER0_PRESCALING           64 /* must match the configuration for TIMER0 in main */
#define TOLERATED_DEVIATION_PPT     10 /* max clock deviation before we tune in 1/10 % */
/* derived constants: */
#define EXPECTED_TIMER0_INCREMENT   ((F_CPU / (1000L * TIMER0_PRESCALING)) & 0xff)
#define TOLERATED_DEVIATION         (TOLERATED_DEVIATION_PPT * F_CPU / (1000000 * TIMER0_PRESCALING))
static void tuneOsccal(void) {
 static uchar lastTimer0Value;
 uchar t=timer0Snapshot;
 char d=t-lastTimer0Value;
 lastTimer0Value=t;
 d-=EXPECTED_TIMER0_INCREMENT;
 t=OSCCAL;
 if (d>=TOLERATED_DEVIATION+1) t--;
 if (d<-TOLERATED_DEVIATION) t++;
 OSCCAL=t;
}
#endif
//extern uchar usbTxLen;
extern uchar usbMsgLen;

int main(void) {
// continue initialization that does not fit into interrupt vector table
 asm volatile(
"	out	0x3E,r31\n"	// SPH
"	out	0x3D,r30\n"	// SPL
"	clr	r1\n"
"1:	st	-Z,r1\n"	// clear entire BSS
"	cpi	r30,0x60\n"	// this code is ATmega8 specific
"	cpc	r31,r1\n"
"	brne	1b");
// initialize two bytes of data declared inside USBDRV.C,
// because there is no data copy routine (this here is shorter)
 usbTxLen = USBPID_NAK;	
 usbMsgLen = USB_NO_MSG; 
 ExpectEP1OutToken=USBPID_DATA0;

 uchar SofCmp = 0;
#ifdef PCINT0_vect	// hier genutztes wesentliches Merkmal der ATmegaX8
 MCUSR = 0;
 wdt_disable();		// Watchdog-Zustand überlebt Reset! Ausschalten!
#if F_CPU == 12800000
 PRR = 0xCF;		// getaktete Peripherie außer Timer0 totlegen
 TCCR0B = 0x03;		// Vorteiler 64 (für Synchronisation benötigt)
#else
 PRR = 0xEF;		// Komplette Peripherie totlegen
#endif
 ACSR |= 0x80;		// Analogvergleicher ausschalten - 70 µA Strom sparen
 usbInit();		// Pegelwechsel-Interrupt aktivieren
 sei();
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 sleep_enable();
 if (USBIN & USBMASK) {	// kein SE0?
  sleep_cpu();		// Ohne Oszillator schlafen bis zum Pegelwechsel
 }			// warten bis SE0
 set_sleep_mode(SLEEP_MODE_STANDBY);	// kein ...IDLE weil keine Peripherie
 wdt_enable(WDTO_15MS);	// Watchdog ist Strom sparender als ein Zeitgeber,
			// ein 3-ms-Zeitgeber wäre aber die USB-konforme Lösung
#else		// ATmega8-Kode: Polling per Watchdog-Timer (WDT-Fuse gesetzt)
 uchar mcucsr = MCUCSR;
 MCUCSR = 0;
 ACSR |= 0x80;		// Analogvergleicher ausschalten - 70 µA Strom sparen
#if F_CPU == 12800000
 TCCR0 = 0x03;		// Vorteiler 64 (für Synchronisation benötigt)
#endif
 sleep_enable();	// normaler Schlafmodus
 if (mcucsr & (1 << WDRF)) {
  if (USBIN & USBMASK) {// kein SE0?
   set_sleep_mode(SLEEP_MODE_PWR_DOWN);
   sleep_cpu();		// Schlafen ohne sei() - bis zum nächsten Watchdog
  }			// Gemessene mittlere Stromaufnahme: 500 µA (hurra!)
 }
 usbInit();
 sei();
#endif
 eeprom_read_block(&g,(uchar*)0xFFF9,7);
 if (g.Feature == 0xFF) g.Feature = 0;	// ungebrannt als 0 annehmen
 if (g.osccal && g.osccal!=0xFF) OSCCAL = g.osccal;
	// beschleunigt auf 12,8 MHz ziehen, außer bei erster Inbetriebnahme
 initIOPorts();
 LptOn();
 if (g.ecr&0x1F) g.ecr=0x20;// bidirektionalen PS/2-Modus voreinstellen
 out402(g.ecr);

 for(;;) {		// Hauptschleife
  uchar t = ECR_Bits;
  if (t & 0x04) SppXfer();		// SPP-FIFO-Transfer prüfen
  else if (t & 0x08) EcpXfer();		// ECP-FIFO-Transfer prüfen
  else if (USBIN & USBMASK) sleep_cpu();// Schlafen, außer bei SE0
  t = usbSofCount;
  if (SofCmp != t || !(USBIN & USBMASK)) {
   wdt_reset();		// alle 1 ms (oder öfter bei SE0) Watchdog beruhigen
  }
  usbPoll();
  if (SofCmp != t) {	// SOF eingetroffen?
#if F_CPU == 12800000
   if ((uchar)(t-SofCmp)==1) tuneOsccal();
#endif
   SofCmp = t;
   Led_On1ms();		// LED blinken lassen
   if (eeprom_is_ready()) {	// Feature-Byte im EEPROM nachführen
    if (eeprom_read_byte((uchar*)0xFFFB) != g.Feature)
      eeprom_write_byte((uchar*)0xFFFB, g.Feature);
    else if (eeprom_read_byte((uchar*)0xFFFA) != OSCCAL)
      eeprom_write_byte((uchar*)0xFFFA, OSCCAL);	// OSCCAL nachführen
   }
  }
 }
}

/*** Untersuchung Stromverbrauch USB-Standby (und Normalbetrieb) ***
 * ATmega8, 12,8 MHz:
 Wie erwartet läuft alle 15 ms der RC-Oszillator kurz an, um den Pegel
 bei ICP zu prüfen. Die Länge des USB-Resets (SE0) ist hier unkritisch,
 weil der interne Oszillator schnell hochläuft.
 Der mittlere Stromverbrauch liegt bei < 500 µA, also < 300 µA des ATmega8
 und 200 µA des Pullups.
 Im Normalbetrieb beträgt die Stromaufnahme datenblattgerechte 7 mA.
 * ATmegaX8:
 ATmega48 nicht untersucht; die Firmware ist bereits größer als 4 KB.
*/

Detected encoding: UTF-80