#include "convac.h"
#include <avr/interrupt.h>
/* Realisiert serielle Schnittstelle über USB, so einfach wie möglich
Es existiert eine einfache Hintertür zum Auslösen von Backup/Restore via USB:
"J" = Backup, 1024 Bytes sind die Antwort
"K" = Restore, 1024 Bytes sind zu senden
Das Backup enthält in den ersten 8 Bytes die Konfiguration,
danach folgt der Baum, beginnend mit der Globalen Sicherheit,
wobei deren Schritt-Zeit (die ersten 2 Bytes) ungenutzt sind.
An die Kanalnamen kommt man derzeit nicht heran;
zum Schreiben ist ohnehin der Platz zu knapp.
*/
byte usbCfg;
static const char E0_SSH=3; // 8 Byte CONTROL I/O
static const char E1_SSH=6; // 64 Byte BULK OUT (Maximum für Bulk)
static const char E2_SSH=6; // 64 Byte BULK IN
static const char E3_SSH=3; // 8 Byte INTERR IN
#define W(x) byte(x),byte((x)>>8)
static const PROGMEM byte DeviceDesc[]={
18, // bLength
1, // bDescType Device
W(0x0200), // USB version supported
2, // bDeviceClass Communications
0, // bDeviceSubclass
0, // bDeviceProtocol
1<<E0_SSH, // bMaxPacketSize0 8
W(0x03EB), // Atmel
W(0x2045), // CDC Demo
W(0x0100), //
0, // iVendor
0, // iProduct
0, // iSerialNo
1 // bNumConfig
};
static const PROGMEM byte ConfigDesc[67]={
9, // bLength
2, // bDescType Config
W(sizeof ConfigDesc), // wTotalLength
2, // bNumInterfaces
1, // bConfigValue
0, // iConfig
0xC0, // bmAttributes
0, // bMaxPower
// interface descriptor
9, // bLength
4, // bDescType Iface
0, // bInterfaceNumber
0, // bAlternateSetting (only one)
1, // bNumEndpoints
2, // bIfaceClass communication
2, // bIfaceSubclass abstract control model
1, // bIfaceProtocol V.25ter, common AT commands
0, // iIface
// HEADER_FUNCTIONAL_DESCRIPTOR
5, // bLength
0x24, // bDescType CS_INTERFACE
0, // bDescSubtype header
W(0x0110), // bcdCDC specification version 1.1
// ABSTRACT_CONTROL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR
4, // bLength
0x24, // bDescType CS_INTERFACE
2, // bDescSubtype abstract control management
0x02, // bmCapabilities
// UNION_FUNCTIONAL_DESCRIPTOR
5, // bLength
0x24, // bDescType CS_INTERFACE
6, // bDescSubtype union
0, // bMasterIface
1, // bSlaveIface0
// CALL_MANAGEMENT_FUNCTIONAL_DESCRIPTOR
5, // bLength
0x24, // bDescType CS_INTERFACE
1, // bDescSubtype call management
3, // bmCapabilities
1, // dDataIface
// ENDPOINT_DESCRIPTOR_3_IN
7, // bLength
5, // bDescType ENDPOINT
0x83, // bEndpointAddress 3 IN
0x03, // bmAttributes interrupt
W(1<<E3_SSH), // wMaxPacketSize 8
2, // bInterval 2 ms
// INTERFACE_DESCRIPTOR_1
9, // bLength
4, // bDescType IFACE
1, // bIfaceNumber
0, // bAlternateSetting
2, // bNumEndpoints
0x0A, // bIfaceClass data
0, // bIfaceSubclass
0, // bIfaceProtocol
0, // iIface
// ENDPOINT_DESCRIPTOR_1_OUT
7, // bLength
5, // bDescType ENDPOINT
0x01, // bEndpointAddress 1 OUT
0x02, // bmAttributes bulk
W(1<<E1_SSH), // wMaxPacketSize 64 (Maximum)
0, // bInterval
// ENDPOINT_DESCRIPTOR_2_IN
7, // bLength
5, // bDescType ENDPOINT
0x82, // bEndpointAddress 2 IN
0x02, // bmAttributes bulk
W(1<<E2_SSH), // wMaxPacketSize 64 (Maximum)
0, // bInterval
};
static const PROGMEM byte LineCoding[] = {
W(9600),W(0), // Baudrate
0,
0,
8 // Datenbits
};
// Interrut-Daten
static const PROGMEM byte IntData[]= { // 8 Byte
0xA1,0x20,0,0,0,0,2,0
};
#undef W
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 void usbPollEp0() {
UENUM=0; // Endpoint 0 betreffend
if (UEINTX&4) { // OUT angekommen?
UEINTX=~4; // Daten ignorieren
}
if (UEINTX&8) { // SETUP angekommen?
byte len;
const byte*addr;
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;
byte wLengthH=UEDATX;
UEINTX=~8; // Jetzt erst(!) alles löschen außer TXINI
// Der Einfachheit halber (Platzmangel!!) ohne bmRequestType auszuwerten,
// ohne String-Deskriptoren
// wLength=wLengthH<<8|wLengthL;
switch (bRequest) {
// Standard-Requests
case 0: { // Get Status
UEDATX=bmRequestType?0:1; // Self Powered (bei Device Request)
UEDATX=0;
}goto okay;
case 1: // Clear Feature
case 3: goto okay; // Set Feature
case 5: { // Set Address (wValueL)
UEINTX=0; // mit 0-Byte-IN-Paket beantworten
UDADDR=wValueL; // laut Datenblatt, praktisch nicht erforderlich
while (!(UEINTX&1)); // warten bis abgeschickt
UDADDR=wValueL|0x80;
}return;
case 6: switch (wValueH) { // Get Descriptor
case 1: addr=DeviceDesc; break;
case 2: addr=ConfigDesc; len=sizeof ConfigDesc; goto gd2;
default: goto stall;
}
len=pgm_read_byte(addr);
gd2:
if (!wLengthH && len>wLengthL) len=wLengthL;
while (len) {
byte n;
while (!((n=UEINTX)&0x0D));// warten bis Interrupt
if (n&0x0C) return; // Ende bei (unerwartetem) OUT- oder SETUP-Paket (15h kommt vorbei!)
n=1<<E0_SSH; if (n>len) n=len;
len-=n;
asm volatile(
"1: lpm r0,Z+ \n"
" sts %1,r0 \n"
" dec %2 \n"
" brne 1b \n"
:"+z"(addr):"m"(UEDATX),"r"(n));
// do UEDATX=pgm_read_byte(addr++); while (--n);
UEINTX&=~1;
}
return;
case 8: { // Get Configuration
UEDATX=usbCfg&1;
}goto okay;
case 9: if (wValueL<2) { // Set Configuration
if (usbCfg=wValueL) { // alles einfach gepuffert (reicht hier)
UENUM=1; UECONX=1; UECFG0X=0x80; UECFG1X=2|E1_SSH-3<<4; // EP1OUT 64 Byte
UENUM=2; UECONX=1; UECFG0X=0x81; UECFG1X=2|E2_SSH-3<<4; // EP2IN 64 Byte
UENUM=3; UECONX=1; UECFG0X=0xC1; UECFG1X=2|E3_SSH-3<<4; // EP3IN 16 Byte
UENUM=0;
}else{
UERST=0x0E;
UERST=0; // alle 3 Endpoints rücksetzen
}
goto okay;
}break;
case 10: { // Get Interface (= Alternate Setting)
UEDATX=0;
}/*nobreak*/;
case 11: goto okay; // Set Interface (ignorieren)
// Klassenspezifische Requests
case 0x20: { // Set Line Coding (wLength==7)
//outlen=wLengthL;
while (!(UEINTX&4));
UEINTX=~4;
/*
byte n;
while (!(n=UEINTX)&0x4C); // warte bis OUT-Daten gekommen sind
if (n&0x40) goto stall; // NAKIN: Host will Status zu früh
if (n&0x08) return; // Ende bei unerwartetem SETUP (kommt nie)
beep(60,10);
byte*addr=modeBuffer;
n=sizeof modeBuffer;
do *addr++=UEDATX; while (--n); // Daten einlesen (schlimmstenfalls ungültige)
UEINTX&=~4;
*/
}goto okay; // 0-Byte-IN-Paket absenden
case 0x21: { // Get Line Coding (wLength==7)
#if 1
addr=LineCoding;
len=sizeof LineCoding;
}goto gd2;
#else
byte n=7;
if (!wLengthH&&n>wLengthL) n=wLengthL;
addr=modeBuffer;
if (n) do UEDATX=*addr++; while (--n);
}goto okay; // Daten absenden, OUT-Quittung ignorieren
#endif
case 0x22: usbCfg=0x21; // Set Control Line State (wLength==0)
case 0x23: // Send Break (wLength==0?)
okay:UEINTX=~1; return;
}
stall:UECONX=0x21; // Alles andere: STALL aktivieren
}
}
// Abfrage von E1 bietet eine Hintertür für künftiges Windows-Programm,
// welches Backup und Restore anschubst.
// Der Kennbuchstabe "J" (Backup) und "K" (Restore) muss mit einem 1-Byte-Paket kommen
static void usbPollEp1() {
UENUM=1;
if (UEINTX&0x80) { // Daten angekommen?
if (UEBCLX==1) switch (UEDATX) {
case 'J': usbBackup(); break; // Daten via E2 herausschießen
case 'K': usbRestore(); break; // Daten einlesen
}
UEINTX=0; // Datenblock zurück zur SIE
}
}
static void usbPollEp3() {
UENUM=3;
if (usbCfg&0x30 && UEINTX&1) { // Puffer frei?
UEINTX=~1; // Interrupt quittieren (extra erforderlich?)
if (usbCfg&0x20) { // Erster Interrupt: 8 Bytes
asm volatile(
"1: lpm r0,Z+ \n"
" sts %1,r0 \n"
" dec %2 \n"
" brne 1b \n"
::"z"(IntData),"m"(UEDATX),"r"(sizeof IntData));
}else{ // Zweiter Interrupt: 2 Bytes
UEDATX=3;
UEDATX=0;
}
UEINTX=0; // Interruptdaten abschicken
usbCfg-=0x10; // erst 2x → 1x, dann 1x → 0x0x
}
}
void usbPoll() {
if (USBINT&1) { // Ab- oder Anstecken des Hosts?
USBINT=0;
if (USBSTA&1) { // Angesteckt
UDCON=0; // Pullup aktivieren
}else{ // Abgesteckt (Dazu muss am Arduino der Transistor T1 ausgelötet werden!)
UDCON=1; // Pullup deaktivieren
USBCON=0xB0; // Takt deaktivieren
usbCfg=0; // Meldung für Geigel-Menü
}
}
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();
if (!(usbCfg&0x20)) usbPollEp1();
usbPollEp3();
}
bool usbBackup() {
const byte*p=eedata.space;
do{
ticStart();
do{
idle(); // idle() ruft usbPoll auf, was UENUM verändert
if (ticCheck(250)) return false;
UENUM=2;
}while (!(UEINTX&0x80)); // Warten bis Puffer frei
while (UEINTX&0x20) UEDATX=*p++; // Puffer (64 Byte) füllen
UEINTX=0; // Puffer abschicken
}while(p!=eedata.space+sizeof eedata.space); // 16 Puffer à 64 Byte = 1024 Byte
do{
idle();
UENUM=2;
}while (!(UEINTX&0x80)); // Warten bis Puffer frei
UEINTX=0; // Null-Byte-Paket abschicken (Endekennung)
return true;
}
bool usbRestore() {
usbCfg|=0x20; // Bit 5 sperrt usbPollEp1()
byte*p=eedata.space;
for(;;) {
ticStart();
do {
idle(); // PROBLEM: EEPROM-Update ist hier besser nicht erlaubt, sonst drohen inkonsistente Daten!
if (ticCheck(250)) break;
UENUM=1;
}while (!(UEINTX&0x80)); // Warten bis Paket angekommen
while (UEINTX&0x20) {
*p++=UEDATX; // Speicher füllen (1 KByte in beliebiger Stückelung)
if (p==eedata.space+sizeof eedata.space) break;
};
UEINTX=0; // Ausgelesener Puffer zur SIE
}
usbCfg&=~0x20;
return p>=eedata.space+12; // Minimum für leere Rezeptliste
}
void usbPurge() {
UENUM=1; // OUT-Puffer leeren
while (UEINTX&0x80) UEINTX=0;
UENUM=2;
while (UESTA0X&0x03) {
UEINTX=0x05; // Kill Bank IN
while (UEINTX&0x04); // warte bis fertig
}
UERST=0x04;
UERST=0; // Keine Daten, die auf Abholung warten
}
Vorgefundene Kodierung: UTF-8 | 0
|