#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
}
Vorgefundene Kodierung: UTF-8 | 0
|