Source file: /~heha/ewa/Ofen/prozess.zip/avr/o1/usb.cpp

#include "usb.h"
/* Realisiert HID-Schnittstelle über USB sowie WinUSB/WebUSB, so einfach wie möglich
 */
static byte usbCfg;
static const char E0_SSH=6;	// 64 Byte	Control I/O
static const char E1_SSH=4;	// 16 Byte	Interrupt In (für HID)
static const char E3_SSH=4;	// 16 Byte	Interrupt In (für WebUSB)

#define W(x) byte(x),byte((x)>>8)
#define D(x) W(x),W(x>>16)

static const PROGMEM byte HidRepDesc[]={
 0x06,W(65441),		//G Usage Page: Unknown
 0x09,1,		//L Usage: 1
 0xA1,1,		//M Collection
  0x85,1,		//M  Report ID (1)
  0x19,56,		//L  Usage Minimum
  0x29,63,		//L  Usage Maximum
  0x75,1,		//G  Report Size
  0x95,8,		//G  Report Count
  0x81,2,		//M  Input: Variable
  0x75,8,		//G  Report Size: 8 bits
  0x95,1,		//G  Report Count
  0x15,0,		//G  Logical Minimum
  0x26,W(255),		//G  Logical Maximum
  0xA4,			//M  Push
  0x19,64,		//L   Usage Minimum: Relais
  0x29,65,		//L   Usage Maximum
  0x95,2,		//G   Report Count: 2
  0x81,2,		//M   Input: Variable
  0x09,66,		//L   Usage: Prozesszeit
  0x75,16,		//G   Report Size
  0x95,1,		//G   Report Count: 1
  0x27,D(64800),	//G   Logical Maximum
  0x47,D(64800),	//G   Physical Maximum: 18 h
  0x66,W(0x1001),	//G   Unit: Sekunde
  0x81,2,		//M   Input: Variable
  0x09,67,		//L   Usage: Heizleistung
//  0x75,16,		//G   Report Size: 16 bits
  0x55,7,		//G   Unit Exponent: 7 wegen (cm und g statt m und kg)
  0x66,W(0xD121),	//G   Unit: Watt (kgm²s-³)
  0x81,2,		//M   Input: Variable
  0x95,4,		//G   Report Count: 4
  0x26,W(4000),		//G   Logical Maximum: Viertelgrad
  0x36,W(273),		//G   Physical Minimum: 0 °C
  0x46,W(1273),		//G   Physical Maximum: 1000 °C
  0x19,68,		//L   Usage Minimum: Solltemperatur
  0x29,72,		//L   Usage Maximum: Isttemperatur3
  0x55,0,		//G   Unit Exponent: 0
  0x67,D(0x00010001),	//G   Unit: Kelvin
  0x81,2,		//M   Input: Variable
  0xB4,			//M  Pop
// Arrays von Strukturen können im HID-Deskriptor nicht sinnvoll abgebildet werden,
// daher Repräsentation als simples Byte-Array
  0x85,2,		//M  Report ID
  0x09,2,		//L  Usage: 2 (2..4 Reglerparameter)
  0x95,1+MAXPID*14,	//G  Report Count
  0xB1,2,		//M  Feature: Variable

  0x85,4,		//M  Report ID
  0x09,4,		//L  Usage: 4 (2..20 Prozessschritte)
  0x95,1+MAXSTEP*6,	//G  Report Count
  0xB1,2,		//M  Feature: Variable

  0x85,12,		//M  Report ID
  0x09,12,		//L  Usage: 12 (Historiendaten löschen)
  0x95,1,		//G  Report Count
  0x91,1,		//M  Output: Constant (maximaler Teiler der Abspeicherrate)

  0x85,13,		//M  Report ID
  0x09,13,		//L  Usage: 13 (Historiendaten lesen)
  0x96,W(9+HISTLEN),	//G  Report Count
  0x81,2,		//M  Input: Variable

  0x85,14,		//M  Report ID
  0x09,14,		//L  Usage: 14 (Cookies lesen/schreiben)
  0x96,W(COOKIESIZE),	//G  Report Count
  0xB1,2,		//M  Feature: Variable

  0x85,15,		//M  Report ID
  0x09,15,		//L  Usage: 15 (Konstanten)
  0x95,KONSTLEN,	//G  Report Count
  0x81,1,		//M  Input: Constant

// Die beeinflussbaren Werte des Input-Reports sind in einzelne Output-Reports verpackt,
// ohne zugehörige Einheit und Skalierung.
  0x25,1,		//G  Logical Maximum
  0x85,56,		//M  Report ID
  0x75,1,		//G  Report Size
  0x95,1,		//G  Report Count
  0x09,56,		//L  Usage: Start/Stopp Regler
  0x91,2,		//M  Output: Variable
  0x95,7,		//G  Report Count
  0x91,1,		//M  Output: Constant

  0x85,57,		//M  Report ID
  0x95,1,		//G  Report Count
  0x09,57,		//L  Usage: Start/Stopp Prozess
  0x91,2,		//M  Output: Variable
  0x95,7,		//G  Report Count
  0x91,1,		//M  Output: Constant

  0x85,99,		//M  Report ID
  0x95,1,		//G  Report Count
  0x09,99,		//L  Usage: Reboot
  0x91,2,		//M  Output: Variable
  0x95,7,		//G  Report Count
  0x91,1,		//M  Output: Constant

  0x85,64,		//M  Report ID
  0x75,8,		//G  Report Size
  0x95,1,		//G  Report Count (ab hier)
  0x09,64,		//L  Usage: Relais
  0x91,2,		//M  Output: Variable
  
  0x85,66,		//M  Report ID
  0x75,16,		//G  Report Size
  0x09,66,		//L  Usage: Prozesszeit
  0x91,2,		//M  Output: Variable

  0x85,67,		//M  Report ID
  0x09,67,		//L  Usage: Heizleistung
  0x91,2,		//M  Output: Variable

  0x85,68,		//M  Report ID
  0x09,68,		//L  Usage: Solltemperatur
  0x91,2,		//M  Output: Variable

  0x85,3,		//M  Report ID
  0x09,3,		//L  Usage: 3 (Schwingungspaketlänge, 100..400)
  0xB1,2,		//M  Feature: Variable
  0x09,67,		//L   Usage: Heizleistung
  0xB1,2,		//M  Feature: Variable
  0xB1,2,		//M  Feature: Variable

 0xC0};			//M End Collection
static_assert(sizeof HidRepDesc<256,"Too large");

static const PROGMEM byte DeviceDesc[]={
 18,		// bLength
 1,		// bDescType		Device
 W(0x0210),	// USB version supported
 0,		// bDeviceClass		See interface
 0,		// bDeviceSubclass
 0,		// bDeviceProtocol
 1<<E0_SSH,	// bMaxPacketSize0	8
 W(0x16C0),	// 			Voti
 W(0x27D9),	// 			HID no keyboard/mouse/joystick(27D9)
 W(0x0200),	//
 1,		// iVendor
 2,		// iProduct
 3,		// iSerialNo
 1		// bNumConfig
};

static const PROGMEM byte ConfigDesc[9+9+9+7+9+7]={
 9,		// bLength
 2,		// bDescType		Config
 W(sizeof ConfigDesc),	// wTotalLength
 2,		// bNumInterfaces
 1,		// bConfigValue
 0,		// iConfig
 0xC0,		// bmAttributes		SelfPower
 50,		// bMaxPower (100 mA)
// interface descriptor
 9,		// bLength
 4,		// bDescType		Iface
 0,		// bInterfaceNumber
 0,		// bAlternateSetting	(only one)
 1,		// bNumEndpoints
 3,		// bIfaceClass		HID
 0,		// bIfaceSubclass
 0,		// bIfaceProtocol
 4,		// iIface
// HID descriptor
 9,		// bLength
 0x21,		// bDescType
 W(0x0110),	// bcdHID
 0,		// bCountryCode
 1,		// bNumDescriptors
 0x22,		// bDescriptorType	Report 
 W(sizeof HidRepDesc),	// wDescriptorLength
// Endpoint descriptor
 7,		// bLength
 5,		// bDescType		ENDPOINT
 0x81,		// bEndpointAddress	1 IN
 0x03,		// bmAttributes		interrupt
 W(1<<E1_SSH),	// wMaxPacketSize	16
 250,		// bInterval		250 ms
// interface descriptor
 9,		// bLength
 4,		// bDescType		Iface
 1,		// bInterfaceNumber
 0,		// bAlternateSetting	(only one)
 1,		// bNumEndpoints
 0xFF,		// bIfaceClass		vendor-specific
 0,		// bIfaceSubclass
 0,		// bIfaceProtocol
 5,		// iIface
// Endpoint descriptor
 7,		// bLength
 5,		// bDescType		ENDPOINT
 0x83,		// bEndpointAddress	3 IN
 0x03,		// bmAttributes		interrupt
 W(1<<E3_SSH),	// wMaxPacketSize	16
 250,		// bInterval		250 ms
};

// erforderlich für WebUSB:
static const PROGMEM byte BOS_DESCRIPTOR[] = {
 5,		// Length
 0x0F,		// Binary Object Store descriptor
 W(57),		// Total length
 2,		// Number of device capabilities
// WebUSB Platform Capability descriptor (bVendorCode == 0x01).
 0x18,		// Length
 0x10,		// Device Capability descriptor
 0x05,		// Platform Capability descriptor
 0x00,		// Reserved
 D(0x3408B638),W(0x09A9),W(0x47A0),W(0xFD8B),0xA0,0x76,0x88,0x15,0xB6,0x65,  // WebUSB GUID
 W(0x0100),	// Version 1.0
 0x01,		// Vendor request code	(-> bRequest)
 0x01,		// Landing page		(-> wValueL)
// Microsoft OS 2.0 Platform Capability Descriptor (MS_VendorCode == 0x02)
 0x1C,		// Length
 0x10,		// Device Capability descriptor
 0x05,		// Platform Capability descriptor
 0x00,		// Reserved
 D(0xD8DD60DF),W(0x4589),W(0x4CC7),W(0xD29C),0x65,0x9D,0x9E,0x64,0x8A,0x9F,  // MS OS 2.0 GUID
 D(0x06030000),	// Windows version
 W(0x00B2),	// Descriptor set length
 0x02,		// Vendor request code
 0x00		// Alternate enumeration code
};
static_assert(sizeof BOS_DESCRIPTOR==57,"Wrong struct!");
#undef D
#undef W

// erforderlich für WebUSB für Windows
static const PROGMEM struct{
 struct{
  word descsize,headertype;
  dword windowsversion;
  word descsetsize;
 }DescSetHeader;
 word ConfSubsetHeader[4];
 word FuncSubsetHeader[4];
 word CompatIdDesc[2];
 char compatID[8];
 char subcompatID[8];
 struct{
  word wLength,wDescriptorType,wPropertyDataType,wPropertyNameLength;
  wchar_t bPropertyName[21];
  word wPropertyDataLength;
  wchar_t bPropertyData[40];
 }CUSTOM_PROPERTY;
}MS_OS_20_DESCRIPTOR = {
// Microsoft OS 2.0 descriptor set header (table 10)
 {0x000A,	// Descriptor size (10 bytes)
  0x0000,	// MS OS 2.0 descriptor set header
  0x06030000,	// Windows version (8.1) (0x06030000)
  0x00B2},	// Size, MS OS 2.0 descriptor set
// Microsoft OS 2.0 configuration subset header
 {0x0008,	// Descriptor size (8 bytes)
  0x0001,	// MS OS 2.0 configuration subset header
  0x0000,	// bConfigurationValue
  0x00A8},	// Size, MS OS 2.0 configuration subset
// Microsoft OS 2.0 function subset header
 {0x0008,	// Descriptor size (8 bytes)
  0x0002,	// MS OS 2.0 function subset header
  0x0001,	// interface number
  0x00A0},	// Size, MS OS 2.0 function subset
// Microsoft OS 2.0 compatible ID descriptor (table 13)
 {20,		// wLength
  3},		// MS_OS_20_FEATURE_COMPATIBLE_ID
 "WINUSB",
 "",
 {4+21+1+40<<1,//wLength: 
  4,		// wDescriptorType: MS_OS_20_FEATURE_REG_PROPERTY: 0x04 (Table 9)
  7,		//wPropertyDataType: REG_MULTI_SZ (Table 15)
  21<<1,	//wPropertyNameLength:
  L"DeviceInterfaceGUIDs",	//bPropertyName
  40<<1,	// wPropertyDataLength
  L"{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}"	//bPropertyData
 }
};
static_assert(sizeof MS_OS_20_DESCRIPTOR==23+66<<1,"Wrong struct!");
static_assert(sizeof MS_OS_20_DESCRIPTOR<256,"Too large!");

// erforderlich für WebUSB
static const PROGMEM char LandingPageUrlDesc[] =
 "\x2C"		// strlen(landingPageUrl)+3
 "\x03"		// descriptorType(String)
 "\x01"		// landingPageScheme(https://)
 "www.tu-chemnitz.de/~heha/ewa/Ofen/app.htm";
static_assert(sizeof LandingPageUrlDesc==0x2D,"Wrong struct!");

static const PROGMEM wchar_t strLang[] =
// High-Teil = DTYPE_String, Low-Teil = (String-Länge+1)*2	
 L"\x0304"	L"\x0407"			// (0) Länge 1: deutsch
 L"\x0322"	L"TU Chemnitz, EWA"		// (1) Länge 16
 L"\x0322"	L"Temperaturregler"		// (2) Länge 16
 L"\x0330"	L"heha@hrz.tu-chemnitz.de"	// (3) Länge 23
 L"\x0308"	L"HID"				// (4) Länge 3
 L"\x030E"	L"WebUSB";			// (5) Länge 6


// Nur von Assembler aufzurufen, macht *Z++ aus Daten- oder Flashspeicher
extern "C" void getbyte() { asm(
"	sbrs	ZH,7	\n"
"	 ld	r0,Z+	\n"
"	sbrc	ZH,7	\n"
"	 lpm	r0,Z+	\n");}

void usbInit() {
 UHWCON=1;
 USBCON=0xB0;	// Ubus aktivieren, aber ohne Takt
 USBINT=1;	// beim nächsten usbPoll USB aktivieren oder deaktivieren lassen
}	// Dokumentationsfehler: USBCON.7 muss 1 sein damit UDCON.0 0 bleibt!

static word wLength;

void usbEp0Send(const void*addr, word len) {
 bool zlp=len<wLength;
 if (len>wLength) len=wLength;
 UENUM=0;
 byte n;
 do{
  while (!((n=UEINTX)&0x0D));	// warten bis Interrupt — oder USB abgesteckt
  if (n&0x0C) return;		// Ende bei (unerwartetem) OUT- oder SETUP-Paket (15h kommt vorbei!)
  n=1<<E0_SSH; if (n>len) n=len;
  if (n) asm volatile(
"	push	%2	\n"
"1:	rcall	getbyte	\n"
"	sts	%1,r0	\n"
"	dec	%2	\n"
"	brne	1b	\n"
"	pop	%2	\n"
:"+z"(addr):"m"(UEDATX),"r"(n));
//  do UEDATX=pgm_read_byte(addr++); while (--n);
  UEINTX=~1;
 }while (len-=n || zlp && n==1<<E0_SSH);
}

void usbEp0Recv(byte*addr, word len) {
 UENUM=0;
 while (len) {
  byte n;
  while (!((n=UEINTX)&0x0C));	// warten bis Interrupt — oder USB abgesteckt
  if (n&0x08) return;		// Ende bei unerwartetem SETUP-Paket
  n=1<<E0_SSH; if (n>len) n=len;
  len-=n;
  do *addr++=UEDATX; while(--n);
  UEINTX=~4;
 }
}

static void usbPollEp0() {
 UENUM=0;		// Endpoint 0 betreffend
 if (UEINTX&4) {	// OUT angekommen?
  UEINTX=~4;		// Daten ignorieren
 }
 if (UEINTX&8) {	// SETUP angekommen?
// Das SETUP-Paket in einzelne Bytes zu stecken ermöglicht Optimierung,
// sofern nicht Unterprogramme (damit) aufgerufen werden müssen.
  byte bmRequestType=UEDATX;
  byte bRequest=UEDATX;
  byte wValueL=UEDATX;
  byte wValueH=UEDATX;
  byte wIndexL __attribute__((unused)) =UEDATX;
  byte wIndexH __attribute__((unused)) =UEDATX;
  byte wLengthL=UEDATX;
  wLength=UEDATX<<8|wLengthL;
  UEINTX=~8;		// Jetzt erst(!) alles löschen außer TXINI
  byte len;
  const byte*addr;	// kann in RAM oder Flash (≥ 0x8000) zeigen
#define RETURN_OKAY {UEINTX=0; return;}
#define RETURN_STALL {UECONX=0x21; return;}
  switch (bmRequestType) {
// Standard-Requests
   case 0x00: switch (bRequest) {	// OUT, DEVICE
    case 1:			// Clear Feature
    case 3: RETURN_OKAY;	// Set Feature
    case 5: {			// Set Address (wValueL)
     UEINTX=0;			// mit 0-Byte-IN-Paket beantworten
     while (!(UEINTX&1));	// warten bis abgeschickt (TODO: Deadlock bei USB-Reset!)
     UDADDR=wValueL|0x80;	// jetzt erst Adresse setzen
    }return;
    case 9: if (wValueL<2) {	// Set Configuration
     if (usbCfg=wValueL) {	// alles einfach gepuffert (reicht hier)
      UENUM=1; UECONX=1; UECFG0X=0xC1; UECFG1X=2|E1_SSH-3<<4;	// EP1IN 16 Byte
      UENUM=3; UECONX=1; UECFG0X=0xC1; UECFG1X=2|E3_SSH-3<<4;	// EP3IN 16 Byte
      UENUM=0;
     }else{
      UERST=0x7E; 
      UERST=0;	// alle Endpoints rücksetzen
     }
     RETURN_OKAY;
    }break;
   }break;
   case 0x01: switch (bRequest) {	// OUT, STD, IFACE[wIndexL]
    case 11: RETURN_OKAY;		// Set Interface (ignorieren)
   }break;
   case 0x21: switch (bRequest) {	// OUT, CLASS, IFACE[wIndexL]
    case 9: onEp0SetReport(wValueL); return;// Set Report (Report ID = wValueL)
   }break;
   case 0x80: switch (bRequest) {	// IN, STD, DEVICE
    case 0: {			// Get Status
     UEDATX=1;			// Self Powered
     UEDATX=0;
    }RETURN_OKAY;
    case 6: switch (wValueH) {	// Get Descriptor
     case 1: addr=FP(DeviceDesc); goto gd1;
     case 2: addr=FP(ConfigDesc); len=sizeof ConfigDesc; goto gd2;
     case 3: {
      if (wValueL>5) RETURN_STALL;
      for (addr=FP(strLang);wValueL;wValueL--) addr+=pgm_read_byte(addr);
     }goto gd1;
     case 15: addr=FP(BOS_DESCRIPTOR); len=sizeof BOS_DESCRIPTOR; goto gd2;
     case 0x22: goto usbtreeview;	// das hilft usbtreeview.exe über einen Windows-Bug
    }RETURN_STALL;
    case 8: {		// Get Configuration
     UEDATX=usbCfg&1;
    }RETURN_OKAY;
   }break;
   case 0x81: switch (bRequest) {	// IN, STD, IFACE[wIndexL]
    case 0: {			// Get Status
     UEDATX=0;
     UEDATX=0;
    }RETURN_OKAY;
    case 6: switch (wValueH) {	// Get Descriptor
     case 0x22: usbtreeview: addr=FP(HidRepDesc); len=sizeof HidRepDesc; goto gd2;
    }RETURN_STALL;
    case 10: {			// Get Interface (= Alternate Setting)
     UEDATX=0;
    }RETURN_OKAY;
   }break;
   case 0xA1: switch (bRequest) {	// IN, CLASS, IFACE[wIndexL]
    case 1: onEp0GetReport(wValueL); return;// Get Report (Report ID = wValueL)
   }break;
   case 0xC0: switch (bRequest) {	// IN, VENDOR, DEVICE
    case 2: addr=FP(&MS_OS_20_DESCRIPTOR); len=sizeof MS_OS_20_DESCRIPTOR; goto gd2;	// wIndex=7
    case 1: addr=FP(LandingPageUrlDesc);	// wIndex=2, wValueL=1
gd1:	    len=pgm_read_byte(addr);
gd2:	    usbEp0Send(addr,len);
	    return;
   }break;
  }
  RETURN_STALL;		// Alles andere: STALL aktivieren
 }
#undef RETURN_OKAY
#undef RETURN_STALL
}

void usbPoll() {
 if (USBINT&1) {	// Ab- oder Anstecken des Hosts?
  USBINT=0;
  if (USBSTA&1) {	// Angesteckt
   UDCON=0;		// Pullup aktivieren
  }else{		// Abgesteckt
   UDCON=1;		// Pullup deaktivieren
   USBCON=0xB0;		// Takt deaktivieren
   usbCfg=0;
  }
 }
 byte udint=UDINT;
 UDINT=0;
 if (udint&0x10 && USBCON&0x20) {	// Aktivität && Takt eingefroren?
  PLLCSR=0x12;		// PLL aktivieren (für 16-MHz-Quarz)
  while(!(PLLCSR&1));	// warten bis eingerastet
  USBCON=0x90;		// Takt aktivieren
 }
 if (udint&1) {		// USB-Suspend
  USBCON=0xB0;		// Takt deaktivieren
  PLLCSR=0;		// PLL deaktivieren
 }
 if (udint&8) {		// USB-Reset-Ende?
  usbCfg=0;		// Nicht konfiguriert (Adresse wird von Hardware zurückgesetzt)
  UENUM=0;
  UECONX=1;		// Endpoint 0 aktivieren
  UECFG1X=2|E0_SSH-3<<4;// 8 Bytes für Endpoint 0 im DPRAM reservieren
 }
 usbPollEp0();
}

void usbEp1Send(const void*data,byte len) {	// 0 < len <= 16!
 if (!(usbCfg&1)) return;	// kein USB: Nichts zu tun!
// while (UENUM=1,!(UEINTX&0x80)) usbPoll();
 if (UENUM=1,!(UEINTX&0x80)) UEINTX=0xFF;	// Kill Bank = Nicht abgeholte (veraltete) Daten verwerfen
 asm volatile(
"1:	rcall	getbyte	\n"
"	sts	%1,r0	\n"
"	dec	%2	\n"
"	brne	1b	\n"
:"+z"(data):"m"(UEDATX),"r"(len));
 UEINTX=0;	// Puffer abschicken
}

void usbEp3Send(const void*data,byte len) {	// 0 < len <= 16!
 if (!(usbCfg&1)) return;	// kein USB: Nichts zu tun!
 if (UENUM=3,!(UEINTX&0x80)) UEINTX=0xFF;	// Kill Bank = Nicht abgeholte (veraltete) Daten verwerfen
 asm volatile(
"1:	rcall	getbyte	\n"
"	sts	%1,r0	\n"
"	dec	%2	\n"
"	brne	1b	\n"
:"+z"(data):"m"(UEDATX),"r"(len));
 UEINTX=0;	// Puffer abschicken
}
Detected encoding: UTF-80