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