Source file: /~heha/mb-iwp/Antriebe/Linak-Servo/hea-fw-230331.zip/usb.cpp

#include "hea.h"
#include <string>	// std::char_traits
#include <array>	// std::array

namespace usb{
enum{
 EP0S=64,	// 64 Byte	Control I/O
 EP1TS=64,	// 64 Byte	Interrupt In (für HID)
 EP1RS=0,
 EP2TS=64,	// 64 Byte	Bulk In (für WebUSB)
 EP2RS=0
};
// Zum Verwalten der Buffer Table
struct PMA_USAGE {
 byte header[0x20],	// Platz für BTABLE (4 Einträge, 3 genutzt)
  ep0t[EP0S],	// EP0-Schreibpuffer
  ep0r[EP0S],	// EP0-Lesepuffer, alles einfach gepuffert
  ep1t[EP1TS],	// EP1-Schreibpuffer
  ep1r[EP1RS],	// EP1-Lesepuffer
  ep2t[EP2TS],	// EP2-Schreibpuffer
  ep2r[EP2RS];	// EP2-Lesepuffer
 PMA_USAGE()=delete;
};
static_assert(sizeof(PMA_USAGE)<=512,"PMA voll!");	// USB/CAN SRAM

/* USB Descriptors */
#define W(x) byte(x),byte((x)>>8)	// erst Low dann High
#define D(x) W(x),W((x)>>16)
static const byte HidRepDesc[] = {
 0x06, W(0xFF20),	// G Usage Page (Sensor)
 0x09, 0x33,		// L Usage (Bewegung)
 0xA1, 1,		// M Collection (Physical)

// feature report 2: Bremskraftvorgabe, Triggerschwellen für Kraftstoß
  0x85, 2,		//  G Report-ID
  0x95, 1,		//  G Report Count
  0x75, 8,		//  G Report Size
  0x15, 0,		//  G Logical Minimum
  0x25, 255,		//  G Logical Maximum
  0x09, 0x62,		//  L Usage: Boolean Switch Array
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(1)
  0x75, 16,		//  G Report Size
  0x26, W(4095),	//  G Logical Maximum
  0x09, 0x65,		//  L Usage: Pressure (Bremskraftvorgabe)
  0xB1, 2,		//  M Input (Data_Var_Abs)	(3:2)
  0x95, 2,		//  G Report Count
  0x09, 100,		//  L Usage: Force (Triggerschwelle für Kraftstoß-Report)
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(5:4, 7:6)
  0x95, 4,		//  G Report Count
  0x19, 0x6D,		//  L Usage Minimum: Prätrigger-Zeit in Samples
  0x29, 0x70,		//  L Usage Maximum: Kanalzuordnung
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(9:8, 11:10, 13:12, 15:14)

//input report 1: Verdrehsensor, Potenziometer, Kraft, Messschieber
  0x85, 1,		//  G Report-ID
  0x95, 1,		//  G Report Count
  0x75, 8,		//  G Report Size
  0x25, 255,		//  G Logical Maximum
  0x09, 98,		//  L Usage: Boolean Switch Array
  0x81, 2,		//  M Input (Data_Var_Abs)	(1)
  0x75, 16,		//  G Report Size

  0x95, 4,		//  G Report Count
  0x26, W(4095),	//  G Logical Maximum
// Hand-Potenziometer
  0x09, 101,		//  L Usage: Hand-Potenziometer
  0x79, 101,		//  L String Index
  0xA4,			//  G Push
   0x35, 0,		//  G Physical Minimum
   0x45, 100,		//  G Physical Maximum (ohne Einheit: %)
   0x81, 2,		//  M Input (Data_Var_Abs)	(9:2)
  0xB4,			//  G Pop
// Kraftmessdose
  0x09, 102,		//  L Usage: Kraftmesser
  0x79, 102,		//  L String Index
  0xA4,			//  G Push
   0x15, 0,		//  G Logical Minimum
   0x26, W(778),	//  G Logical Maximum (hier: für kleine Kräfte)
   0x35, 0,		//  G Physical Minimum
   0x45, 10,		//  G Physical Maximum
   0x55, 8,		//  G Unit Exponent (10^8) d.h. „kN“ darstellen
   0x66, W(0xE111),	//  G Unit (g×cm÷s²)
   0x81, 2,		//  M Input (Data_Var_Abs)	(17:10)
  0xB4,			//  G Pop
// Weg-Poti
  0x09, 103,		//  L Usage: Weg-Potenziometer
  0x79, 103,		//  L String Index
  0xA4,			//  G Push
   0x15, 39,		//  G Logical Minimum
   0x26, W(3878),	//  G Logical Maximum
   0x35, 0,		//  G Physical Minimum
   0x45, 50,		//  G Physical Maximum
   0x55, byte(-1),	//  G Unit Exponent (10^-1) d.h. „mm“ darstellen
   0x66, W(0x0011),	//  G Unit (cm)
   0x81, 2,		//  M Input (Data_Var_Abs)	(25:18)
  0xB4,			//  G Pop
// ADC6
  0x09, 104,		//  L Usage: Verdrehsensor (ADC6)
  0x79, 104,		//  L String Index
  0x81, 2,		//  M Input (Data_Var_Abs)	(33:26)
// ADC1
  0x09, 105,		//  L Usage: (ADC1)
  0x79, 105,		//  L String Index
  0x81, 2,		//  M Input (Data_Var_Abs)	(41:34)

  0x95, 1,		//  G Report Count
// Für den Messschieber die Umrechnungsanweisung mitliefern
  0x09, 106,		//  L Usage: Messschieber
  0x79, 106,		//  L String Index
  0xA4,			//  G Push
   0x16, W(-15118),	//  G Logical Minimum
   0x26, W(+15118),	//  G Logical Maximum
   0x36, W(-150),	//  G Physical Minimum
   0x46, W(+150),	//  G Physical Maximum
   0x55, byte(-1),	//  G Unit Exponent (10^-1) d.h. „mm“ darstellen
   0x66, W(0x0011),	//  G Unit (cm)
   0x81, 2,		//  M Input (Data_Var_Abs)	(43:42)
  0xB4,			//  G Pop
// Kraftstoß  
  0x09, 107,		//  L Usage: Kraftstoß (Integral der positiven Werte über 1 ms)
  0x79, 107,		//  L String Index
  0xA4,			//  G Push
   0x15, 0,		//  G Logical Minimum
   0x26, W(778),	//  G Logical Maximum (hier: für kleine Kräfte)
   0x35, 0,		//  G Physical Minimum
   0x45, 10,		//  G Physical Maximum
   0x55, 5,		//  G Unit Exponent (10^5) d.h. „Ns“ darstellen
   0x66, W(0xF111),	//  G Unit (g×cm÷s)
   0x81, 2,		//  M Input (Data_Var_Abs)	(45:44)
  0xB4,			//  G Pop
// Kraft-Maximum (gleiche Umrechnung wie bei Usage 102)
  0x09, 108,		//  L Usage: Kraft-Maximum im Kraftstoß
  0x79, 108,		//  L String Index
  0x81, 2,		//  M Input (Data_Var_Abs)	(47:46)
// Wegstrecke (gleiche Umrechnung wie bei Usage 103)
  0x09, 109,		//  L Usage: Zurückgelegter Weg
  0x79, 109,		//  L String Index
  0x81, 2,		//  M Input (Data_Var_Abs)	(47:46)

//feature report 4: Wert vom/zum Linak-Servo, die serielle Schnittstelle ersetzend
// (nicht implementiert)
  0x85, 4,		//  G Report-ID
  0x75, 8,		//  G Report Size
  0x25, 255,		//  G Logical Maximum
  0x0A, W(0x5C0),	//  L Usage: Custom (Objektadresse im Linak-Servo, Bit 7 = lesen)
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(1)
  0x75, 16,		//  G Report Size
  0x26, W(65535),	//  G Logical Maximum
  0x0A, W(0x5C1),	//  L Usage: Custom Value 1 (Objektwert im Linak-Servo)
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(3:2)

//feature report 3: Oszilloskopdaten (High-Speed-Abtastung bis zu 4 Kanäle)
  0x85, 3,		//  G Report-ID
  0x75, 8,		//  G Report Size
  0x25, 255,		//  G Logical Maximum
  0x09, 116,		//  L Usage: Kanalzahl
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(1)
  0x75, 16,		//  G Report Size
  0x26, W(65535),	//  G Logical Maximum
  0x09, 117,		//  L Usage: Samplezahl
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(3:2)
  0x09, 118,		//  L Usage: Interleaved Samples
  0x96, W(elemof(repT.data)),	//  G Report Count (viel!)
  0xB1, 2,		//  M Feature (Data_Var_Abs)	(viel:4)
// Dieser Report hat mit >2000 Bytes eine erhebliche Länge!
// Als Input-Report ungeeignet.

//feature report 99: Reboot
  0x85, 99,		//  G Report-ID
  0x75, 8,		//  G Report Size
  0x95, 1,		//  G Report Count
  0x25, 1,		//  G Logical Maximum
  0x0A, W(0x5C2),	//  L Usage: Custom Value 2
  0xB1, 2,		//  M Feature (Data_Arr_Abs)

//output report 42: Manual Trigger
  0x85, 42,		//  G Report-ID
  0x0A, W(0x5C3),	//  L Usage: Custom Value 3
  0x91, 2,		//  M Output (Data_Var_Abs)	// wird ignoriert
 0xC0,		// M End Collection
};

static const byte DeviceDesc[] = {
 18,		// bLength
 1,		// bDescType		Device
 W(0x0210),	// bcdUSB		2.10
 0,		// bDeviceClass		See Iface Descs
 0,		// bDeviceSubClass
 0,		// bDeviceProtocol
 EP0S,		// bMaxPacketSize0
 W(0x16C0),	// idVendor
 W(0x27D9),	// idProduct
 W(0x2111),	// bcdDevice		Jahr+Monat
 1,		// iManufacturer
 2,		// iProduct
 3,		// iSerialNumber
 1 		// bNumConfigurations
};

static const byte ConfigDesc[9+9+9+7+9+7] = {
 9,		// bLength
 2,		// bDescType		Config
 W(sizeof ConfigDesc),// wTotalLength
 2,		// bNumInterfaces
 1,		// bConfigValue
 0,		// iConfig
 0x80,		// bmAttributes		Busversorgt
 50>>1,		// bMaxPower		50 mA

 9,		// bLength
 4,		// bDescType		Iface
 0,		// bIfaceNumber		0
 0,		// bAlternateSetting
 1,		// bNumEndpoints
 3,		// bIfaceClass		HID
 0,		// bIfaceSubClass
 0,		// bIfaceProtocol
 4,		// iIface

 9,		// bLength
 0x21,		// bDescType		HID
 W(0x0111),	// bcdHID		1.11
 0,		// bCountryCode
 1,		// bNumDescriptors
 0x22,		// bDescType[0]		Report
 W(sizeof HidRepDesc),// wDescriptorLength[0]

 7,		// bLength
 5,		// bDescType		Endpoint
 0x81,		// bEndpointAddress	1 IN
 3,		// bmAttributes		interrupt, nur für Messdaten
 W(EP1TS),	// wMaxPacketSize
 32,		// bInterval
		// Das betrifft auch Mehrpakete-Transfers!!

 9,		// bLength
 4,		// bDescType		Iface
 1,		// bInterfaceNumber	1
 0,		// bAlternateSetting	(only this one)
 1,		// bNumEndpoints
 0xFF,		// bIfaceClass		vendor-specific
 0,		// bIfaceSubclass
 0,		// bIfaceProtocol
 5,		// iIface

 7,		// bLength
 5,		// bDescType		ENDPOINT
 0x82,		// bEndpointAddress	2 IN
 2,		// bmAttributes		bulk, für Mess- und Oszidaten
 W(EP2TS),	// wMaxPacketSize
 0,		// bInterval		wird immer aggresiv abgefragt
};

// erforderlich für WebUSB für Windows
static const byte MsOs20Desc[] = {
// Microsoft OS 2.0 descriptor set header (table 10)
 W(10),	// Descriptor size (10 bytes)
 W(0),	// MS OS 2.0 descriptor set header
 D(0x06030000),	// Windows version (8.1) (0x06030000)
 W(178),	// Size, MS OS 2.0 descriptor set
// Microsoft OS 2.0 configuration subset header
 W(8),	// Descriptor size (8 bytes)
 W(1),	// MS OS 2.0 configuration subset header
 W(0),	// bConfigurationValue
 W(168),	// Size, MS OS 2.0 configuration subset
// Microsoft OS 2.0 function subset header
 W(8),	// Descriptor size (8 bytes)
 W(2),	// MS OS 2.0 function subset header
 W(1),	// interface number
 W(160),	// Size, MS OS 2.0 function subset
// Microsoft OS 2.0 compatible ID descriptor (table 13)
 W(20),		// wLength
 W(3),		// MS_OS_20_FEATURE_COMPATIBLE_ID
 'W','I','N','U','S','B',0,0,
 0,0,0,0,0,0,0,0,
 W(4+21+1+40<<1),//wLength: 
 W(4),		// wDescriptorType: MS_OS_20_FEATURE_REG_PROPERTY: 0x04 (Table 9)
 W(7),		//wPropertyDataType: REG_MULTI_SZ (Table 15)
 W(21<<1),	//wPropertyNameLength:
 W('D'),W('e'),W('v'),W('i'),W('c'),W('e'),W('I'),W('n'),
 W('t'),W('e'),W('r'),W('f'),W('a'),W('c'),W('e'),W('G'),
 W('U'),W('I'),W('D'),W('s'),W( 0 ),	//bPropertyName
 W(40<<1),	// wPropertyDataLength
 W('{'),W('9'),W('7'),W('5'),W('F'),W('4'),W('4'),W('D'),
 W('9'),W('-'),W('0'),W('D'),W('0'),W('8'),W('-'),W('4'),
 W('3'),W('F'),W('D'),W('-'),W('8'),W('B'),W('3'),W('E'),
 W('-'),W('1'),W('2'),W('7'),W('C'),W('A'),W('8'),W('A'),
 W('F'),W('F'),W('F'),W('9'),W('D'),W('}'),W( 0 ),W( 0 )	//bPropertyData
};
static_assert(sizeof MsOs20Desc==23+66<<1,"Wrong struct!");
static_assert(sizeof MsOs20Desc==178,"Falsch!");

// erforderlich für WebUSB:
static const byte BosDesc[5+24+28] = {
 5,		// Length
 0x0F,		// Binary Object Store descriptor
 W(sizeof BosDesc),	// Total length
 2,		// Number of device capabilities
// WebUSB Platform Capability descriptor (bVendorCode == 0x01).
 24,		// 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)
 28,		// 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(sizeof MsOs20Desc),	// Descriptor set length
 0x02,		// Vendor request code	(-> bRequest)
 0x00		// Alternate enumeration code
};
static_assert(sizeof BosDesc==57,"Wrong struct!");
#undef D
#undef W

template<size_t N>constexpr auto urldesc(byte proto, const char*s) {
 constexpr auto L=3+N;
 static_assert(L<256,"String zu lang!");
 std::array<byte,L> A={L,3,proto};
 for (size_t i=0; i<N; i++) A[3+i] = s[i];
 return A;
}
#define URLDESC(p,s) (urldesc<std::char_traits<char8_t>::length(u8##s)>(p,s))

// erforderlich für WebUSB
static const auto UrlDesc =
 URLDESC(1,"www.tu-chemnitz.de/~heha/mb-iwp/Antriebe/Linak-Servo/app.htm");
#undef URLDESC


static constexpr volatile uint32_t* bufferAddress(unsigned btable_addr) {
 return reinterpret_cast<volatile uint32_t*>(PMAAddr + btable_addr*2);
}
// The <src> argument is the first here to help place the return value in the same register
// <src> must be aligned! (divisable by 4)
static void fromPMA(unsigned btable_addr,byte*dst,unsigned len) {
 const volatile uint32_t*src=bufferAddress(btable_addr);
 union{
  byte*pb;
  uint16_t*ph;
 }d={dst};
 auto l=len>>1;
 if (l) do *d.ph++=*src++; while (--l);	// copy 2 bytes
 if (len&1) *d.pb=*src;	// copy low byte - the return value cannot be used for continuing!
}

static void toPMA(unsigned btable_addr,const byte*src,unsigned len) {
 volatile uint32_t*dst=bufferAddress(btable_addr);
 union{
  const byte*pcb;
  const uint16_t*pch;
 }s={src};		// Quellzeiger, darf unausgerichtet sein, auf Bytes und Halfwords
 auto l=len>>1;
 if (l) do *dst++=*s.pch++; while (--l);	// copy 2 bytes
 if (len&1) *dst=*s.pcb;	// copy low byte - continuation impossible
}

SetupPacket setup;	// last received setup packet
byte DevConfig;		// current configuration value (0 after USB reset)

bool send(byte ep, const byte*data, unsigned length) {
 if (!DevConfig) return false;	// don't allow sending over EP1+ when not configured
 byte flags=ep; ep&=0x0F;
 constexpr unsigned eplen=64;	// Hier: Überall gleich
 for(;;) {
  unsigned l=length;
  if (!l && flags&noZlp) return true;
  if (USB.ISTR&0x7C00) return false;	// PMA-OVR, ERR, WKUP, SUSP, RESET: Alles Fehler
  byte timeout = flags&onBusyWait ? 105 : 5;	// Millisekunden = USB-Frames
//230331: 2 Frames Wartezeit sind zu wenig, da kommt manchmal nur ein Trace-Bruchstück (Edge, nicht Chrome)
//230331: 5 helfen aber auch nicht! Außerdem kommt rätselhafterweise das Last-Packet in der richtigen Länge durch.
// Dürfte nach dieser Logik gar nicht vorkommen.
  byte tic = USB.FNR;
  while ((USB.EPR[ep]&EPTX_STAT)==EP_TX_VALID) {
   idle();	// warten bis vorheriges Paket abgesendet
   if (USB.ISTR&0x7C00) return false;	// PMA-OVR, ERR, WKUP, SUSP, RESET: Alles Fehler
   if (byte(USB.FNR-tic)>=timeout) break;
  }
// Der Endpoint muss sich in einem Zustand befinden, bei der dieser auf
// IN-Tokens mit NAK antwortet, also leer sein. Keine Doppelpufferung.
  switch (USB.EPR[ep]&EPTX_STAT) {
   case EP_TX_NAK: break;	// in Ordnung
   case EP_TX_VALID: if (flags&onBusySend) break; // Daten überschreiben
   [[fallthrough]];	// Geht hier SET_TX_STATUS(ep,EP_TX_NAK)?
   default: return false;
  }
  debug::workstart();
  flags&=~(onBusySend|onBusyWait); // Folgepakete mit Standardverhalten
  if (l>eplen) l=eplen;
  toPMA(BTABLE[ep].ADDR_TX,data,l);
  BTABLE[ep].COUNT_TX=l;	// kann auch ein 0-Byte-Paket sein
  SET_TX_STATUS(ep,EP_TX_VALID);
  debug::workend();
  if (l<eplen) return true;	// Short-Packet ist das letzte
  length-=l;
  data+=l;
 }
}

// Warten ohne Timeout
static bool waitSent0() {
 while (!(USB.EPR[0]&EP_CTR_TX)) {
  if (USB.ISTR&(ISTR_RESET|ISTR_SUSP)) return false;
  idle();
 }
 USB.EPR[0]=0x8200;	// Tx-Interrupt löschen
 return true;
}

bool send0(const byte*data, unsigned len) {
 auto cmp=len<=>setup.wLength;
 if (cmp>0) len=setup.wLength;	// limit data length according to setup-requested length
 for(;;){
  unsigned l=len; if (l>EP0S) l=EP0S;
  toPMA(offsetof(PMA_USAGE,ep0t),data,l);
  BTABLE[0].COUNT_TX=l;
  SET_TX_STATUS(0,EP_TX_VALID);	// absenden
  data+=l;
  len-=l;
  if (!(len || cmp<0 && l==EP0S)) break;	// raus wenn fertig, aber Null-Byte-Paket nicht vergessen
  if (!waitSent0()) return false;// warten bis abgeholt, raus bei USB-Reset u.ä.
 }
 USB.EPR[0]=0x8380;	// EP_KIND setzen: Nur Null-Byte-OUT akzeptieren
 return true;
}

static bool ack0() {
 BTABLE[0].COUNT_TX=0;
 SET_TX_STATUS(0,EP_TX_VALID);
 return true;
}

static bool waitRecv(byte ep) {
 auto&epr=USB.EPR[ep];
 while (!(epr&EP_CTR_RX)) {
  if (USB.ISTR&(ISTR_RESET|ISTR_SUSP)) return false;
  idle();
 }
 epr=0x0280;
 return true;
}

/*
static bool onHidOut(byte repid) {
 if (repid) return false;
 unsigned len=BTABLE[0].COUNT_RX&0x1FF;
 if (len>sizeof repO0) return false;
 fromPMA(offsetof(PMA_USAGE,ep0r),repO0,len);
 USB.EPR[0]=0x9280;	// change NAK to VALID
// if (!((flashAddress+=len)&(PAGE_SIZE-1))) {	// entire page received?
// }else SET_RX_STATUS(0,EP_RX_VALID);	// expect next packet
 return true;
}
*/

bool recv(byte ep, byte*data,unsigned length,unsigned*pLen) {
 for (unsigned len=0; len<length;) {
  if (!waitRecv(ep)) return false;	// USB-Reset oder sonstwas doofes
  uint16_t l=BTABLE[ep].COUNT_RX&0x1FF;
  if (l>length-len) return false;	// Babble
  if (!l) return pLen ? *pLen=len, true : false;
  		// Null-Byte-Paket: Je nach pLen erlaubt oder nicht
		// TODO: Short-Packet-Erkennung (erfordert ggf. weiteren Parameter)
  fromPMA(offsetof(PMA_USAGE,ep0r),data,l); break;
  USB.EPR[ep]=0x9280;	// change NAK to VALID
  len+=l;
  data+=l;
 }
 return true;
}
//s muss ein gültiger, nullterminierter UTF8-String sein,
//der in nicht mehr als 126 UTF16-Zeichen konvertiert
static byte buildStringDesc(byte*d,const char*s) {
 auto*p=reinterpret_cast<char16_t*>(d+2);
 d[1]=3;
 for (uint32_t c; c=*s++; *p++=c) {
  if (!(c&0x80)) continue;//		0000 0000 0000 0000 0000 0000 0aaa aaaa
  c=c<<6|*s++&0x3F;	//		0000 0000 0000 0000 0011 ?aaa aabb bbbb
  if (c&0x800) {	// 3-Byte-UTF8:	0000 0000 0000 0000 0011 !?aa aabb bbbb
   c=c<<6|*s++&0x3F;	//		0000 0000 0000 111? aaaa bbbb bbcc cccc
   if (c&0x10000) {	// 4-Byte-UTF8:	1111 1111 1111 111! 0aaa bbbb bbcc cccc
    c=c<<6|*s++&0x3F;	//		1111 1111 110a aabb bbbb cccc ccdd dddd
    c-=0x10000;		//		1111 1111 1100 AABB bbbb cccc ccdd dddd
    *p++=0xD800|c>>10&0x3FF;	// High Surrogate	    1101 10AA BBbb bbcc
    c=0xDC00|c&0x3FF;		// Low Surrogate	    1101 11cc ccdd dddd
   }
  }else{		// 2-Byte-UTF8:	0000 0000 0000 0000 0011 0aaa aabb bbbb
   c&=0x7FF;		//		0000 0000 0000 0000 0000 0aaa aabb bbbb
  }
 }
 return d[0]=static_cast<byte>(reinterpret_cast<byte*>(p)-d);
}


static bool onGetDescriptor() {
 const byte*desc;
 unsigned length = 0;
 switch (setup.wValueH) {
  case 1: desc = DeviceDesc; break;
  case 2: desc = ConfigDesc; length = sizeof ConfigDesc; break;
  case 0x0F: desc = BosDesc; length = sizeof BosDesc; break;
  case 0x21: desc = ConfigDesc+9+9; break;
  case 0x22: desc = HidRepDesc; length = sizeof HidRepDesc; break;
  case 3: {
   byte buf[256];
   switch (setup.wValueL) {
    case 0: buildStringDesc(buf,"\xD0\x87"); break;	// 0x0407 = deutsch
    case 1: buildStringDesc(buf,"Henrik Haftmann"); break;
    case 2: buildStringDesc(buf,"Hüftgelenkmontage-Einschlag-Apparat „hea“ 🔨"); break;
    case 3: buildStringDesc(buf,"heha@hrz.tu-chemnitz.de"); break;
    case 4: buildStringDesc(buf,"Einschlag-HID"); break;	// Windoofs: Darf nur ASCII enthalten!!
    case 5: buildStringDesc(buf,"⭐WebUSB"); break;
    case 6: buildStringDesc(buf,"Minimum"); break;
    case 7: buildStringDesc(buf,"Maximum"); break;
    case 8: buildStringDesc(buf,"Mittelwert"); break;
    case 9: buildStringDesc(buf,"Varianz"); break;
    case 101: buildStringDesc(buf,"Brems-Poti/%"); break;
    case 102: buildStringDesc(buf,"Kraft/kN"); break;
    case 103: buildStringDesc(buf,"Weg-Poti/mm"); break;
    case 104: buildStringDesc(buf,"ADC6"); break;
    case 105: buildStringDesc(buf,"ADC1"); break;
    case 106: buildStringDesc(buf,"Messschieber/mm"); break;
    case 107: buildStringDesc(buf,"Kraftstoß/Ns"); break;
    case 108: buildStringDesc(buf,"Kraft-Maximum/kN"); break;
    case 109: buildStringDesc(buf,"Verschiebeweg/mm"); break;
    default: return false;
   }
   desc=buf;
  }break;
  default: return false;
 }
 if (!length) length = desc[0];
 return send0(desc, length);
}


static void initEP(uint32_t ep) {
 MOD_REG(USB.EPR[ep&0x0F],EP_CTR_RX|EP_T_FIELD|EP_KIND|EP_CTR_TX|EPADDR_FIELD,ep);
}

static void onReset() {
 /* Initialize Endpoint 0 */
 initEP(EP_CONTROL|EP_RX_VALID);
 USB.DADDR = DADDR_EF;		// device address = 0 and enable USB function
}
// USB dual-port RAM memory map:
// ARM adr	USB adr	size	(all hex)
// 40006000	0	20	buffer descriptor table (4 entries)
// 40006030	20	40	EP0RX buffer (SETUP and hid-OUT data)
// 400060B0	60	40	EP0TX buffer (IN data, mostly descriptors)
//		A0	40	EP1RX
// 40006130	E0	40	EP1TX buffer (interrupt IN)
//		120	40	EP2RX
//		160	40	EP2TX
// 40006140	1A0	60	free for more buffers (not much!!)
// 40006400	200	-	end of dual-port RAM

byte DevStatus;

static bool onSetup() {
 byte stat[2]={0,0};
 switch (setup.wRequest) {
  case 0x0080: stat[0]=usb::DevStatus; return send0(stat,2);	// Get Status (In, Standard, Device)
  case 0x0081: return send0(stat,2);	// Get Status (In, Standard, Iface)
  case 0x0082: {				// Get Status (In, Standard, Endpoint)
   auto epr=USB.EPR[setup.wIndexL&0x0F];
   if (setup.wIndexL&0x80) epr<<=8;		// IN-Endpoint? TX (Low-Byte) nach RX (High-Byte) schieben
   if (epr&EPRX_STAT==EP_RX_STALL) stat[0]|=1;	// Bit 0 = 1 wenn STALL
   return send0(stat,2);
  }

  case 0x0100: if (setup.wValue!=1) return false;// Clear Feature (Out, Standard, Device)
  usb::DevStatus&=~2; return ack0();		// Remote Wakeup wegnehmen
  case 0x0102: if (setup.wValue) return false;	// Clear Feature (Out, Standard, Endpoint)
  {
   auto&epr=USB.EPR[setup.wIndexL&0x0F];
   if (setup.wIndexL&0x80) SET_TX_STATUS(epr,EP_TX_NAK);
   else SET_RX_STATUS(epr,EP_RX_VALID);
  }return ack0();

  case 0x01C0: return send0(&UrlDesc[0],UrlDesc[0]);
  case 0x02C0: return send0(MsOs20Desc,sizeof MsOs20Desc);
  
  case 0x0300: if (setup.wValue!=1) return false;	// Set Feature (Out, Standard, Device)
  usb::DevStatus|=2; return ack0();		// Remote Wakeup unterstützen
  case 0x0302: if (setup.wValue) return false;	// Set Feature (Out, Standard, Endpoint)
  {
   auto&epr=USB.EPR[setup.wIndexL&0x0F];
   if (setup.wIndexL&0x80) SET_TX_STATUS(epr,EP_TX_STALL);
   else SET_RX_STATUS(epr,EP_RX_STALL);
  }return ack0();

  case 0x0500: {				// Set Address (Out, Standard, Device)
   ack0();
   if (waitSent0()) USB.DADDR = DADDR_EF|setup.wValueL;
  }return true;

  case 0x0680:					// Get Descriptor (In, Standard, Device)
  case 0x0681: return onGetDescriptor(); 	// Get Descriptor (In, Standard, Iface)

  case 0x0880: return send0(&DevConfig,1);	// Get Config (In, Standard, Device)
  case 0x0900: DevConfig=setup.wValueL;		// Set Config (Out, Standard, Device)
	       initEP(EP_RX_NAK|EP_INTERRUPT|EP_TX_NAK|1);	// Reset toggles
	       initEP(EP_RX_NAK|EP_BULK|EP_TX_NAK|2);
	       return ack0();

  case 0x0A81: return send0(stat,1);		// Get Iface (In, Standard, Iface)
  case 0x0B01: return !setup.wValueL && ack0();// Set Iface (Out, Standard, Iface)

  case 0x01A1: return onGetReport(setup);		// Get Report (In, Class, Iface)
  case 0x0921: return onSetReport(setup) && ack0();// Set Report (Out, Class, Iface): don't stall, data follows
 }
 return false;
}

static void onEp0() {
 uint16_t epr = USB.EPR[0];
 if (epr&EP_CTR_RX) {		// OUT and SETUP packets (data reception)
  if (epr&USB_EP0R_SETUP) {
   fromPMA(offsetof(PMA_USAGE,ep0r),setup,8);	// pull setup data
   USB.EPR[0]=0x1280;			// clear EP_CTR_RX bit and change NAK to VALID
   if (!onSetup()) USB.EPR[0]=0x0230;		// Tx: Change NAK to STALL
  }else{
   USB.EPR[0]=0x1280;			// clear EP_CTR_RX bit and change NAK to VALID
  }
  SET_RX_STATUS(0,EP_RX_VALID);
 }
// Check for transferred IN packets, i.e. “emptied-out” PMA buffers
// Normally, more data has to been sent here.
// But as all descriptors are less than 64 Bytes in size, no more data has to be processed here.
 if (epr&EP_CTR_TX) {	// Something sent on EP0
  if (setup.wRequest==0x0500) USB.DADDR = DADDR_EF|setup.wValueL;	// setup data is still available
  USB.EPR[0]=0x8200;			// clear EP_CTR_TX bit
 }
}

static void onEp1() {
 USB.EPR[1]=1|EP_INTERRUPT;	// Endpoint-Interrupt löschen (Hauptschleife weniger belasten)
}

static void onEp2() {
 USB.EPR[2]=2|EP_BULK;		// Endpoint-Interrupt löschen (Hauptschleife weniger belasten)
}

static void onEndpoint(uint16_t status) {
 switch (status&USB_ISTR_EP_ID) {
  case 0: onEp0(); break;
  case 1: onEp1(); break;
  case 2: onEp2(); break;
 }
}

void shutdown() {
 USB.CNTR = CNTR_FRES|CNTR_PDWN;	// USB-Schaltungsteil abschalten
}

void init() {
 DevConfig = 0;
 USB.CNTR = CNTR_FRES;		// PowerDown-Bit löschen
 USB.CNTR = 0;			// Normalzustand
// USB-Puffertabelle initialisieren (ändert sich später nie)
 BTABLE[0].ADDR_TX = offsetof(PMA_USAGE,ep0t);	// 0x400060C0
 BTABLE[0].ADDR_RX = offsetof(PMA_USAGE,ep0r);	// 0x40006040
 BTABLE[0].setRxBufSize(EP0S);
 BTABLE[1].ADDR_TX = offsetof(PMA_USAGE,ep1t);	// 0x40006200
 BTABLE[1].ADDR_RX = offsetof(PMA_USAGE,ep1r);
 if (EP1RS) BTABLE[1].setRxBufSize(EP1RS);
 BTABLE[2].ADDR_TX = offsetof(PMA_USAGE,ep2t);
 BTABLE[2].ADDR_RX = offsetof(PMA_USAGE,ep2r);
 if (EP2RS) BTABLE[2].setRxBufSize(EP2RS);
}

void poll() {
 uint16_t istr = USB.ISTR;	// save interrupts
 USB.ISTR = 0;		// clear interrupts
 if (istr&ISTR_CTR) onEndpoint(istr);	// istr contains endpoint number
 if (istr&ISTR_RESET) onReset();
 if (istr&ISTR_SUSP) {
  USB.CNTR = CNTR_FSUSP|CNTR_LPMODE;
  ::revert_sysclock();
  USB.DADDR = 0;	// disable function and reset USB address
  LED = 1;		// LED aus
 }
 if (istr&ISTR_WKUP) {
  ::set_sysclock();
  USB.CNTR = 0;
 }
 if (istr&(ISTR_SOF|ISTR_ESOF)) {
  LED = BB_BIT(USB.FNR,9);
 }
}
}//namespace
Detected encoding: UTF-80