Source file: /~heha/basteln/Haus/Telefon/Mithören-ISDN/Firmware.zip/ATmega32U4/usb.c

#include "usb.h"
#include <util/delay.h>

/**************************************************************************
 *  Endpoint-Konfiguration
 **************************************************************************/

// Gesamtgröße für ATmega32U4: 832 Byte, hier 2×64+2×16+64 = 224
// Endpints: 6 (EP1..5 gleichartig), Maximalgröße: 512 Byte

#define EP0_SIZE	64
#define DX_EP		4
#define DX_EP_SIZE	16	// D-Datenkanal (Interrupt)
#define B1_EP		3	// Bearer = Audiodaten (Hören+Sprechen)
#define B1_EP_SIZE	36	// doppelt gepuffert -> 128 Byte Verbrauch
// Pro Millisekunde 1 Paket à 64 Byte = 7..9 Stereo-Samples 16 Bit


/**************************************************************************
 *  USB-Deskriptoren
 **************************************************************************/

// Diese Daten liest der Computer beim Anstecken („Enumeration“).

#define W(x) (x)&0xFF,(x)>>8			// word
#define T(x) W((x)&0xFFFF),(uint32_t)(x)>>16	// triple
#define D(x) W((x)&0xFFFF),W((uint32_t)(x)>>16) // dword

static const byte PROGMEM device_desc[] = {
 18,		// bLength
 1,		// descriptor type	Device
 W(0x0200),	// USB version supported
 0,		// USB_CFG_DEVICE_CLASS,
 0,		// USB_CFG_DEVICE_SUBCLASS,
 0,		// protocol
 EP0_SIZE,	// max packet size
 W(0x16c0),	// vendor ID = VOTI, Teilbereich siphec, Teilbereich h#s
 W(0x06B7),	// product ID = Audiogerät: CNC oder ISDN-Sniffer
 W(0x1401),	// version (Jahr/Monat)
 1,		// manufacturer string index
 2,		// product string index
 0,		// serial number string index
 1,		// number of configurations
};

// Via HID wird der D-Kanal (Hören) mitgeschnitten.
// So kann die Caller-ID (Telefonnummer des Gesprächspartners = Anrufers) 
// mitbekommen werden und eine automatische Dateinamensgenerierung erfolgen.
// Ob der D-Kanal auch die Telefonnummer eines ausgehenden Gesprächs
// widerspiegelt ist erst mal unbekannt.
// Das Bit-Destuffing erledigt die Firmware, Prüfsummen o.ä.
// muss das HID-Programm prüfen.
static const byte PROGMEM hid_report_descriptor[] = {
 0x06, W(0xFF31),	// Usage Page 0xFF31 (vendor defined)
 0x09, 0x74,		// Usage 0x74
 0xA1, 0x53,		// Collection 0x53
  0x75, 8,		// report size = 8 bits
  0x15, 0,		// logical minimum = 0
  0x26, W(255),		// logical maximum = 255
  0x95, DX_EP_SIZE,	// report count
  0x09, 0x75,		// usage
  0x81, 0x02,		// Input (array)
 0xC0			// end collection
};

#define CONFIG_DESC_SIZE (9+9+9+7 +9+9+12+7+9 +9+9+7+11+9+7)
// muss <=255 sein!
#define HID_DESC2_OFFSET  (9+9)
#define LAW_OFFSET	(9+9+9+7 +9+9+12+7+9 +9+9+5)

static const byte PROGMEM config_desc[CONFIG_DESC_SIZE] = {
// Configuration Descriptor
 9,		// bLength
 2,		// bDescriptorType	Config
 W(CONFIG_DESC_SIZE),	// wTotalLength
 3,		// bNumInterfaces	HID, Audio Control, In B1 und In B2
 1,		// bConfigurationValue	erste und einzige
 0,		// iConfiguration	ohne Text
 0xC0,		// bmAttributes		Busversorgt, Aufwecken
 100/2,		// MaxPower (in 2 Milliampere)	100 mA

// Interface Descriptor 0
 9,		// bLength
 4,		// bDescriptorType	Interface
 0,		// bInterfaceNumber
 0,		// bAlternateSetting
 1,		// bNumEndpoints
 3,		// bInterfaceClass	HID
 0,		// bInterfaceSubClass
 0,		// bInterfaceProtocol
 3,		// iInterface
// HID interface descriptor
 9,		// bLength
 0x21,		// bDescriptorType
 W(0x0110),	// bcdHID
 0,		// bCountryCode
 1,		// bNumDescriptors
 0x22,		// bDescriptorType
 W(sizeof(hid_report_descriptor)),	// wDescriptorLength
// Endpoint Descriptor
 7,		// bLength
 5,		// bDescriptorType
 DX_EP|0x80,	// bEndpointAddress
 0x03,		// bmAttributes		Interrupt
 W(DX_EP_SIZE),	// wMaxPacketSize
 8,		// bInterval		8 ms genügen vollauf

// Interface Descriptor 1
 9,		// bLength
 4,		// bDescriptorType	Interface
 1,		// bInterfaceNumber
 0,		// bAlternateSetting
 0,		// bNumEndpoints	keine
 1,		// bInterfaceClass	Audio
 1,		// bInterfaceSubClass	Steuerung
 0,		// bInterfaceProtocol
 4,		// iInterface		wird angezeigt als Eingabegerät!
// Audio Control Interface Header Descriptor
 9,		// bLength
 0x24,		// bDescriptorType	Audio Interface
 1,		// bDescriptorSubtype	Header
 W(0x100),	// bcdADC		Audio 1.0
 W(9+12+7+9),	// wTotalLength
 1,		// bInCollection	Anzahl AudioStreaming-Interfaces
 2,		// baInterfaceNr[0]	Interface-Nr. 2 (liefert B1 oder B2)
// Audio Control Input Terminal Descriptor (B1)
 12,		// bLength
 0x24,		// bDescriptorType	Audio Interface
 2,		// bDescriptorSubtype	Input Terminal
 1,		// bTerminalID
 W(0x501),	// wTerminalType	Telefonleitung, „Telefonanschluss“
 0,		// bAssocTerminal	0, nicht bidirektional
 2,		// bNrChannels		Stereo
 W(0),		// wChannelConfig	L+R vorn (normales Stereo)
 0,		// iChannelNames	(für die Katz!)
 5,		// iTerminal		„B1“ (Win7,Linux)
// Audio Control Selector Descriptor (B1 oder B2)
 7,		// bLength
 0x24,		// bDescriptorType	Audio Interface
 5,		// bDescriptorSubtype	Selector Unit
 3,		// bUnitID
 1,		// bNrInPins		1 Eingang
 1,		// baSourceID[1]
 3,		// iSelector		(für die Katz!)
// Audio Control Output Terminal Descriptor
 9,		// bLength
 0x24,		// bDescriptorType	Audio Interface
 3,		// bDescriptorSubtype	Output Terminal
 4,		// bTerminalID
 W(0x101),	// wTerminalType	USB-Streaming
 0,		// bAssocTerminal	0, nicht bidirektional
 3,		// bSourceID		das Input-Terminal
 3,		// iTerminal		(für die Katz!)

// Interface Descriptor 2, Zero-Bandwidth
 9,		// bLength
 4,		// bDescriptorType	Interface
 2,		// bInterfaceNumber
 0,		// bAlternateSetting
 0,		// bNumEndpoints
 1,		// bInterfaceClass	Audio
 2,		// bInterfaceSubClass	Streaming
 0,		// bInterfaceProtocol
 0,		// iInterface		(für die Katz!)
// Interface Descriptor 2, Operational
 9,		// bLength
 4,		// bDescriptorType	Interface
 2,		// bInterfaceNumber
 1,		// bAlternateSetting
 1,		// bNumEndpoints	1x IN (16 Bit PCM stereo)
 1,		// bInterfaceClass	Audio
 2,		// bInterfaceSubClass	Streaming
 0,		// bInterfaceProtocol
 0,		// iInterface		(für die Katz!)
// Audio Streaming Interface Descriptor
 7,		// bLength
 0x24,		// bDescriptorType	Audio Streaming
 1,		// bDescriptorSubtype	Interface
 4,		// bTerminalLink	der B1 oder B2 liefert
 0,		// bDelay
#ifdef LAW_AUDIO
 W(4),		// wFormatTag		A-Law
#else
 W(1),		// wFormatTag		1=PCM, 2=PCM8, 4=A-Law, 5=µ-Law
#endif
// Audio Streaming Format Type Descriptor
 11,		// bLength
 0x24,		// bDescriptorType	Audio Streaming
 2,		// bDescriptorSubtype	Format Type
 1,		// bFormatType		Typ I
 2,		// bNrChannels		(Stereo)
#ifdef LAW_AUDIO
 1,		// bSubframeSize	1 Byte
 8,		// bBitResolution	8 Bit
#else
 2,		// bSubframeSize	2 Byte (Alles außer PCM: 1 Byte)
 16,		// bBitResolution	16 Bit (Alles außer PCM: 8 Bit)
#endif
 1,		// bSamFreqType		Eine Frequenz
 T(8000),	// tSamFreq		8 kHz
// Endpoint Descriptor
 9,		// bLength
 5,		// bDescriptorType	Endpoint
 0x80|B1_EP,	// bEndpointAddress	Direction=IN  Endpoint=3
 0x05,		// bmAttributes		Isochronous  SyncType=Async  Type=Data
 W(B1_EP_SIZE),	// wMaxPacketSize	18 Byte
 W(1),		// wInterval		jede Millisekunde
 0,		// bSyncAddress
// Audio Data Endpoint Descriptor (XP: unnötig)
 7,		// bLength
 0x25,		// bDescriptorType	Audio Endpoint
 1,		// bDescriptorSubtype	General
 0,		// bmAttributes		keine
 0,		// bLockDelayUnits	unspezifiziert weil asynchron
 W(0),		// wLockDelay		keins
};


/********
 * Kode *
 ********/

// Aufruf wenn OTG-Pad Spannung führt
static void usbConnect() {
 UHWCON = 0x01;		// Spannungsregler aktivieren (Bit 0)
 USBCON = 0xB1;		// USB aktivieren, noch ohne Takt (so erforderlich)
 PLLCSR = 0x12;		// PLL: Takt/2, aktivieren
 while (!(PLLCSR&1));	// warte bis PLL bereit
 USBCON = 0x91;		// USB-Takt anlegen
 UDCON  = 0;		// Pullup-Widerstand aktivieren (Bit 0)
 UDINT  = 0;		// alle Interrupts löschen
 usbCfg&=0xF0;
}

static void usbDisconnect() {
 USBCON = 0x11;		// Kein USB, nur noch OTG-Pad
 PLLCSR = 0x10;		// PLL abschalten
 UHWCON = 0;		// kein USB-Spannungsregler
 UDINT  = 0x01;
 usbCfg = 0;
}



#if 0
// USB Device Interrupt - handle all device-level events
// the transmit buffer flushing is triggered by the start of frame
//
ISR(USB_GEN_vect) {
 if (UDINT&1<<SOFI) {
  UDINT=~(1<<SOFI);
  sei();
  if (usbCfg&1) {
   if (usbCfg&4) {	// Isochrone Daten?
    byte j,save=UENUM;
    UENUM=B1_EP;
    for (j=0x40; j<0xC0; j+=8) UEDATX=j;	// Sägezahn 1kHz liefern
    UEINTX=0x3A;
    UENUM=save;
   }
  }
 }
}
#endif


// Misc functions to wait for ready and send/receive packets
static void usb_send_in(void) {
 UEINTX = ~(1<<TXINI);
}
static byte usb_wait_in_ready(void) {
 byte i;
 do i=UEINTX;	// wait for host ready for IN packet
 while (!(i & (1<<TXINI | 1<<RXOUTI)));
 return i & 1<<RXOUTI;
}
static void usb_wait_receive_out(void) {
 while (!(UEINTX & (1<<RXOUTI))) ;
}

static NOINIT byte replyBuf[128];


// Generiert USB-String-Deskriptor (mit UTF-16), hier nur BMP0 oder aus CESU-8
static byte BuildStringFromUTF8(const char *src) {
 uint16_t *d=(uint16_t*)(replyBuf+2);
 byte len;
 for (;;) {
  uint16_t c;
#if 0
  c=pgm_read_byte(src++);
  if (!c) break;
  if ((char)c<0) {
   /*if (c&0x20) c=c<<12 | (pgm_read_byte(src++)&0x3F)<<6 | (pgm_read_byte(src++)&0x3F);
   else*/ c=((byte)c&0x3F)<<6 | (pgm_read_byte(src++)&0x3F);
  }
#else
  asm volatile(
"	lpm	%A0,Z+	\n"
"	clr	%B0	\n"
"	sbrs	%A0,7	\n"	// 0xxx xxxx (7 Bit)?
"	 rjmp	1f	\n"	// ja, fertig
"	mov	%B0,%A0	\n"
"	sbrc	%A0,5	\n"	// 110x xxyy oder 1110 xxxx?
"	 rjmp	2f	\n"
"	lsr	%B0	\n"
"	ror	%A0	\n"
"	lsr	%B0	\n"
"	ror	%A0	\n"
"	andi	%B0,0x07\n"	// 0000 0xxx yy.. ....
"	rjmp	3f	\n"
"2:	andi	%B0,0x0F\n"
"	swap	%B0	\n"
"	lpm	%A0,Z+	\n"	// 10xx xxyy
"	lsl	%A0	\n"
"	lsl	%A0	\n"
"	swap	%A0	\n"	// yy00 xxxx
"	mov	r18,%A0	\n"
"	andi	r18,0x0F\n"
"	or	%B0,r18	\n"
"3:	andi	%A0,0xC0\n"
"	lpm	r18,Z+	\n"	// 10yy yyyy
"	andi	r18,0x3F\n"
"	or	%A0,r18	\n"
"1:			\n"
:"=d"(c),"=z"(src)
:"1"(src)
:"r18");
  if (!c) break;
#endif
  *d++=c;
 }
 len=(byte)((byte*)d-replyBuf);
 replyBuf[0]=len;
 replyBuf[1]=3;			// Länge und ID für String-Deskriptor
 return len;
}

static void usbPollEP0() {
 UENUM = 0;
 if (UEINTX & 1<<RXSTPI) {	// Setup angekommen?
  byte i, n, len;
  const byte *desc_addr;
  byte bmRequestType = UEDATX;
  byte bRequest= UEDATX;
  byte wValueL = UEDATX;
  byte wValueH = UEDATX;
  byte wIndexL = UEDATX;
  byte wIndexH __attribute__((unused)) = UEDATX;
  byte wLengthL= UEDATX;
  byte wLengthH= UEDATX;
  UEINTX=~(1<<RXSTPI|1<<RXOUTI); // vorhergehendes Null-Byte-Out erschlagen

  switch (bmRequestType) {

   case 0x00: switch (bRequest) {	// out, device, device
   
    case 1:				// CLEAR_FEATURE
    case 3: {				// SET_FEATURE
     if (wValueL==1) {			// Feature „Remote Wakeup“
      usb_send_in();			// Null-Byte-IN-Paket absenden
      usbCfg=usbCfg&~2|bRequest&2;	// Bit 1 entsprechend setzen
      return;
     }
    }break;
    
    case 5: {				// SET_ADDRESS
     usb_send_in();			// Null-Byte-IN-Paket absenden
     usb_wait_in_ready();		// warte bis abgeholt
     UDADDR = wValueL|0x80;		// Adresse jetzt setzen
    }return;

    case 9: {				// SET_CONFIGURATION
     if (wValueL<2) {
      usb_send_in();			// Null-Byte-IN-Paket absenden
      usbCfg=wValueL;			// setzt Alternate Settings zurück
      UENUM = DX_EP;
      UECONX= 1;
      UECFG0X=0xC1;			// Interrupt-In
      UECFG1X=0x16;			// 16 Byte doppelt gepuffert
      UERST = 0x1E;			// alle Endpoints rücksetzen
      UERST = 0;
      return;
     }
    }break;

   }break;
    
   case 0x01: switch (bRequest) {	// out, device, interface

    case 11: {				// SET_INTERFACE
     if (wValueL<2 && wIndexL==2) {	// nur das Audio-Data-Interface
      usb_send_in();			// Null-Byte-IN-Paket absenden
      UENUM = B1_EP;			// hier: EP3
      if (wValueL) {			// AS=1: Endpoint aktivieren
       UECONX=1;
       UECFG0X=0x41;			// Isochron-In
       UECFG1X=0x36;			// 64 Byte doppelt gepuffert
       UERST=1<<B1_EP;
       usbCfg|=0x04;
       UERST=0;
      }else{				// AS=0: Endpoint totlegen
       UECONX=0;
       usbCfg&=~0x04;
      }
      return;
     }
    }break;

   }break;
   
   case 0x02: switch (bRequest) {	// out, device, endpoint
// Im Mikrocontroller ist genug Platz um ENDPOINT_HALT zu unterstützen
    case 1:				// CLEAR_FEATURE
    case 3: {				// SET_FEATURE
     if (wValueL==0) {			// Feature 0
      i = wIndexL & 0x7F;		// Endpoint
      if (i-1 < 4) {			// 4 = MAX_ENDPOINT
       usb_send_in();			// Null-Byte-IN-Paket absenden
       UENUM = i;
       if (bRequest&2) {		// SET_FEATURE
	UECONX = (1<<STALLRQ)|(1<<EPEN);
       }else{
	UECONX = (1<<STALLRQC)|(1<<RSTDT)|(1<<EPEN);
	UERST = (1 << i);
	UERST = 0;
       }
       return;
      }
     }
    }break;

   }break;

   case 0x21:				// out, class, interface
   if (wIndexL==1			// AudioControl-Interface
     && bRequest==1			// SET_CUR
     && wLengthL==1) {			// 1 Byte Daten kommen
    usb_wait_receive_out();		// warten bis das Datenbyte kommt
    if (UEDATX>1) usbCfg|=8; else usbCfg&=~8;	// Selektor setzen (1-basiert)
    UEINTX = ~(1<<RXOUTI);		// muss unbedingt sequenziell sein!
    usb_send_in();			// (nicht hiermit zusammenfassbar!)
    return;
   };
   //nobreak;
   case 0x22:				// out, class, endpoint
   if (bRequest==0xFF) {		// AUDIO_REQ_GetStatus
    usb_send_in();			// Kein STALL (sonst geht nichts)
    return;
   }break;
    
   case 0x80: switch (bRequest) {	// in, device, device
    
    case 0: {				// GET_STATUS
     usb_wait_in_ready();		// (sollte unnötig sein!!)
     UEDATX=usbCfg&2;			// Remote-Wakeup-Bit
     UEDATX=0;
     usb_send_in();			// Zwei-Byte-Paket abschicken
    }return;				// Null-Byte-Paket ignorieren
		
    case 6: {				// GET_DESCRIPTOR
     usbCfg|=0x40;
     switch (wValueH) {
      case 1: desc_addr=device_desc; break;
      case 2: {
       desc_addr=config_desc;
       len=sizeof config_desc;
      }goto gd2;
      case 3:
      desc_addr=replyBuf;
      usbCfg&=~0x40;
      switch (wValueL) {	// string
       case 0: len=BuildStringFromUTF8(PSTR("\xD0\x87\xD0\x89")); break;
       // wird zu `07 04 09 04` = 2 Sprachen deutsch und englisch
       case 1: len=BuildStringFromUTF8(PSTR("haftmann#software")); break;
       // bei beiden Sprachen gleich
       case 2: len=BuildStringFromUTF8(
         wIndexL==7	// es genügt den Low-Teil (LANG_ID) auszuwerten
	 ?PSTR("h#s ISDN-Schnüffler")
	 :PSTR("h#s ISDN Sniffer")); break;
       case 3: len=BuildStringFromUTF8(
         wIndexL==7
	 ?PSTR("S0-Bus-Daten")
	 :PSTR("S0 bus data")); break;
       case 4: len=BuildStringFromUTF8(
         wIndexL==7
	 ?PSTR("Hörbare S0-Bus-Daten")	// wird angezeigt, XP: englisch
	 :PSTR("Audible S0 bus data")); break;
       case 5: len=BuildStringFromUTF8(
        wIndexL==7
	?PSTR("ISDN B1-Kanal")
	:PSTR("ISDN B1 channel")); break;
       default: goto stall;
      }goto gd2;
      default: goto stall;
     }
gd1: len=pgm_read_byte(desc_addr);	// take from descriptor
gd2: if (!wLengthH && len>wLengthL) len=wLengthL;	// the lesser of both
     do{
      if (usb_wait_in_ready()) return;	// abort at OUT packet
      n=len;				// send IN packet
      if (n>EP0_SIZE) n=EP0_SIZE;
      for (i=n; i; i--) {
       UEDATX = usbCfg&0x40 ? pgm_read_byte(desc_addr++) : *desc_addr++;
      }
      len-=n;
      usb_send_in();
     }while (len || n==EP0_SIZE);	// ggf. Null-Länge-Paket am Ende
    }return;

    case 8: {				// GET_CONFIGURATION
     usb_wait_in_ready();
     UEDATX = usbCfg&1;
     usb_send_in();
    }return;

   }break;
    
   case 0x81: switch (bRequest) {	// in, device, iface

    case 6: {				// GET_DESCRIPTOR
     usbCfg|=0x40;
     switch (wValueH) {
      case 0x21: desc_addr=config_desc+HID_DESC2_OFFSET; goto gd1;
      case 0x22: desc_addr=hid_report_descriptor; len=sizeof hid_report_descriptor; goto gd2;
     }
    }break;
     
    case 10: {				// GET_INTERFACE
     if (wIndexL<4) {
      usb_wait_in_ready();
      i=0;
      if (wIndexL>=2) i=usbCfg>>wIndexL&1;
      UEDATX=i;
      usb_send_in();
      return;
     }
    }break;

   }break;

   case 0x82: switch (bRequest) {	// in, device, endpoint

    case 0: {				// GET_STATUS
     usb_wait_in_ready();
     i=0;
     UENUM = wIndexL;
     if (UECONX & 1<<STALLRQ) i=1;
     UENUM =0;
     UEDATX=i;
     UEDATX=0;
     usb_send_in();
    }return;

   }break;

   case 0xA1: switch (wIndexL) {	// in, class, interface

    case 0: switch (bRequest) {		// HID-Interface
     case 1: {				// HID_GET_REPORT
      len = wLengthL;
      do{
       if (usb_wait_in_ready()) return;	// abort
       n=len;				// send IN packet
       if (n>EP0_SIZE) n=EP0_SIZE;
       for (i=n; i; i--) UEDATX = 0;
       len-=n;
       usb_send_in();
      }while (len || n==EP0_SIZE);
      return;
     }break;
    }break;

    case 1: switch (bRequest) {		// AudioControl-Interface
     case 0x81: i=usbCfg>>3&1; goto send;	// GET_CUR
     case 0x83: i=1; goto send;		// GET_MAX
     case 0x82:				// GET_MIN
     case 0x84: i=0; send:		// GET_RES
     if (wLengthL==1) {
      UEDATX=i+1;			// 1-basiert (wirklich?)
      usb_send_in();
      return;
     }break;
    }break;
     
   }break;

  }
stall:
  UECONX = 0x21;			// stall
 }
}

void usbPoll() {
// Generellen USB-Interrupt behandeln
 if (USBINT&1) {	// Pegelwechsel am OTG-Pad?
  USBINT=0;
  if (USBSTA&1) usbConnect(); else usbDisconnect();
 }
// USB-Device-Interrupts behandeln
 byte udint=UDINT;	// handliches Register
 UDINT=0x05;		// Interrupts außer SUSPI und SOFI löschen
 if (udint&1<<EORSTI) {// Ende USB-Reset
  UENUM  = 0;		// EP0 einrichten
  UECONX = 1;
  UECFG0X= 0;		// Control-Endpoint
  UECFG1X= 0x32;	// 64 Byte einfach gepuffert
  UEINTX = 4;		// Kill Bank IN??
  usbCfg = 0;		// unkonfiguriert
 }
 if (udint&0x01) {	// Suspend (Hardware löscht WAKEUPI)
  if (!(USBCON&0x20)) {// USB-Takt läuft noch (FRZCLK gelöscht)?
   USBCON=0xB0;		// USB-Takt anhalten (FRZCLK setzen)
   PLLCSR=0x10;		// PLL anhalten (Bit 1)
   onSuspend();
  }
 }
 if (udint&0x10) {	// Wakeup (Hardware löscht SUSPI) — kommt ständig
  if (USBCON&0x20) {	// USB-Takt angehalten (FRZCLK gesetzt)?
   PLLCSR=0x12;		// PLL starten
   onResume();
   while (!(PLLCSR&1));// warte bis PLL eingerastet
   USBCON=0x90;		// USB-Takt anlegen
  }
 }
 usbPollEP0();
}

#ifndef MYSTARTUP	// RETIs stehen sonst direkt in der Interruptvektortabelle
EMPTY_INTERRUPT(USB_GEN_vect);
EMPTY_INTERRUPT(USB_COM_vect);
// Das funktioniert nur deshalb,
// weil nach RETI mindestens ein Befehl des Hauptprogramms abgearbeitet wird,
// bevor die leere ISR wieder aufgerufen wird.
// Daher werden die Interrupts nach sleep() umgehend gesperrt.
// Hier rächt es sich mal wieder,
// dass die AVR-Architektur kein Wakeup ohne Interrupts kennt.
#endif
Detected encoding: UTF-80