Source file: /~heha/mb-iwp/Datenhandschuh/Firmware/Firmware.zip/Glove.c

/* Interface for Fakespace fingertip contact gloves
 * Based on AVRCDC by Osamu Tamura
 * The Fakespace gloves have conductive tissue contacts at each fingertip
 * (so there are 10 contacts in up to 5 groups), and a serial EEPROM
 * inside the SubD9♂ plug, mainly to detect a correctly connected glove.
 * This software auto-detects swapped-connected gloves!
 * Furthermore, this software can work with self-made gloves without EEPROM.
 * In that case, swap detection is done via a simple bridge
 * An extra feature is reporting ground connected fingers,
 * activate it by issuing "G1"
 * This device draws very low standby current (220 µA, mainly for USB pull-up)
-070222	handling "missed" SOFs in main loop (incorrect time reported)
+070225	Saving G and T state in EEPROM
*070225	Ground connections will be reported too, but without extra bits,
	in G0 state
 *
 * Source file properties: tabsize = 8, encoding = utf-8
 */

#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>

#include "usbdrv.h"

#define DEBOUNCE 10	// ms of debouncing time

/*******
 * LED *
 *******/
static uchar Led_T;	// flashing time in ms, 0 = LED is steady
static uchar Led_F;	// flashing frequency in ms (half period)
static uchar Led_C;	// flash-counter (via SOF pulses)

// start LED flashing, called from USB framework
void Led_Start(uchar t) {
 if (!Led_T) {
  Led_C=t;
  PORTC|=0x20;		// switch LED OFF
 }
 Led_T=Led_F=t;
}

// actualize LED state every 1 ms
static void Led_On1ms(void) {
 uchar led_t=Led_T, led_c;	// help compiler optimizing
 if (!led_t) return;
 led_c=Led_C;			// copy to register
 if (!--led_c) {
  PORTC^=0x20;			// toggle LED
  led_c=Led_F;			// reload time
 }
 if (!--led_t) PORTC&=~0x20;	// switch LED ON
 Led_T=led_t;			// save registers back
 Led_C=led_c;
}

/*******************************
 * Detecting connection groups *
 *******************************/
 
// wait in units of 0.3 µs
static void wait_us(uchar w) {
 do __asm("nop"); while(--w);
}
 
// Drive bits (glove fingertip contact) low where <Drive> bit is one,
// and set bits to internal-pullup where <Drive> bit is zero
static void DriveBits(unsigned Drive) {
 uchar x1,x2,x2b;	// SubD9 connector X1 vs. X2 (see schematic)
 x1 =Drive;		// normally: left glove
 x2 =Drive>>8;		// normally: right glove
 x1&=0x1F;
 x2b=(x2>>4)&1;		// PortB bit
 x2<<=4;		// PortD bits
 DDRC|=0x1F;		// drive all fingerstips (catch capacitive coupling)
 DDRD|=0xF0;
 DDRB|=0x01;
 PORTC=(PORTC|0x1F)&~x1; // drive the no-more-used output to HIGH (shortly)
 PORTD=(PORTD|0xF0)&~x2;
 PORTB=(PORTB|0x01)&~x2b;
// wait_us(10);		// wait a bit
 DDRC =(DDRC &0xE0)| x1; // then drive the new output to LOW
 DDRD =(DDRD &0x0F)| x2;
 DDRB =(DDRB &0xFE)| x2b;
}

// Read bits. For fingertips driven to LOW, a high bit is returned
// Bits 5,6,7,13,14,15 are always zero.
// This way, two bytes suitable for serial response are returned.
static unsigned ReadBits(void) {
 usbWord_t ret;
 ret.bytes[0]=PINC|0xE0;
 ret.bytes[1]=(PIND>>4) | ((PINB|0xFE)<<4);
 return ~ret.word;
}

typedef struct {	// The connection detection structure
 uchar N;		// number of connections
 unsigned C[5];		// connection list (maximum 5 pairs)
}conn_t;

static conn_t Conn;	// Detected connections (in order of bits)

register uchar RepGnd asm("r5"); // switch: report ground connection(s)

// count bits in a 16-bit word
static uchar CountBits(unsigned w) {
 uchar i,r=0;
 for (i=0; i<16; i++,w>>=1) if (w&1) r++;
 return r;
}

// Find connection groups (unsorted)
static void FindConnGroups(void) {
 unsigned detected=0xE0E0;	// let auto-skip non-available fingers
 unsigned Drive,Cur;
 Conn.N=0;			// clear list
 Cur=ReadBits();		// find ground connections first
 if (Cur && RepGnd) {
  if (Cur&0x00FF) Cur|=0x0020;
  if (Cur&0xFF00) Cur|=0x2000;	// report with extra bits ("extra fingers") set
 }
 if (CountBits(Cur)>1) {	// NEW: always report if more than 1 bit set
  Conn.C[0]=Cur;		// add to list (Conn.N is always zero here)
  Conn.N++;
 }
 detected|=Cur;			// skip these fingers later (no more an error)
 for (Drive=1; Drive; Drive<<=1) {
  if (Drive&detected) continue;	// skip previously found connections
  DriveBits(Drive);
  wait_us(100);		// wait 33 µs to stabilize capacitive conducted lines
  Cur=ReadBits()&~detected;
  if (!(Cur&Drive)) {
   Conn.N=0;
   return;			// error: line is not driven to LOW
  }
  if (Cur&~Drive) {		// there is a connection
   detected|=Cur;		// add connection for skipping
   Conn.C[Conn.N++]=Cur;	// add connection to list
  }
 }
 DriveBits(0);			// switch off all drivers (only weak pull-ups)
}

static conn_t Checked;	// Unsorted conngroups, constant after DEBOUNCE
register uchar DebounceCounter asm("r4");

// Checks whether a new stable <Conn> is available. If so, 1 is returned.
static uchar StableConnGroups(void) {
 if (memcmp(&Conn,&Checked,1+(Conn.N<<1))) {	// changing
  Checked=Conn;			// copy structure
  DebounceCounter=DEBOUNCE;
  return 0;
 }
 if (!DebounceCounter) return 0;	// not new
 if (--DebounceCounter) return 0;	// not zero
 return 1;
}

static conn_t Sorted;

// Sort the current connection group (new connections first) into <Sorted>
// Returns TRUE when a change occurs, FALSE otherwise
static uchar SortConnGroups(void) {
 static conn_t New;		// <static>: don't allocate onto stack
 uchar i,j,ret=0;
 New.N=0;
 for (i=0; i<Conn.N; i++) {
  unsigned conn=Conn.C[i];	// fetch a new connection
  for (j=0; j<Sorted.N; j++) {
   if (conn==Sorted.C[j]) goto brk1;	// skip known connections
  }
  New.C[New.N++]=conn;		// Add a new connection at queue head
  ret=1;			// report change
brk1:;
 }
 for (j=0; j<Sorted.N; j++) {
  unsigned conn=Sorted.C[j];	// fetch a previous connection
  for (i=0; i<Conn.N; i++) {
   if (conn==Conn.C[i]) {
    New.C[New.N++]=conn;	// Add a known connection (keeping order)
    goto brk2;
   }
  }
  ret=1;			// No more found: a change!
brk2:;
 }
 if (ret) Sorted=New;		// copy structure
 return ret;
}

/**************************
 * Talking to I²C EEPROMs *
 **************************/
register uchar CurI2c asm("r3"); // switch: talking to X1 or X2 EEPROM?

// drive SCL low
static void SCLL(void) {
 if (CurI2c) {
  PORTB&=~0x04;
  DDRB |= 0x04;
 }else{
  PORTD&=~0x01;
  DDRD |= 0x01;
 }
}

// drive SCL high, then put the line in high-impedance with internal pull-up
static void SCLH(void) {
 if (CurI2c) {
  PORTB|=0x04;
  DDRB&=~0x04;
 }else{
  PORTD|=0x01;
  DDRD&=~0x01;
 }
}

// read-in SCL state (wait if LOW)
static uchar SCLR(void) {
 if (CurI2c) return PINB&0x04;
 return             PIND&0x01;
}

// drive SDA low
static void SDAL(void) {
 if (CurI2c) {
  PORTB&=~0x02;
  DDRB |= 0x02;
 }else{
  PORTD&=~0x02;
  DDRD |= 0x02;
 }
}

// drive SDA high, then put the line in high-impedance with internal pull-up
static void SDAH(void) {
 if (CurI2c) {
  PORTB|=0x02;
  DDRB&=~0x02;
 }else{
  PORTD|=0x02;
  DDRD&=~0x02;
 }
}

// read-in SDA state (data bit)
static uchar SDAR(void) {
 if (CurI2c) return PINB&0x02;
 return             PIND&0x02;
}

// pulse SCL (HIGH, then wait, then read SDA, then LOW)
// Include wait cycles here if necessary
static uchar SCLP(void) {
 uchar ret=0;
// wait_us(3);		// wait 1 µs
 SCLH();
 wait_us(6);		// wait another 2 µs - so clock speed is 400 kHz
// do if (SCLR()) break; while(--ret);	// additional wait (I²C spec.)
 ret=SDAR();
 SCLL();
// wait_us(3);
 return ret;
}

// Write a byte onto I²C, returns the status bit
// SCL must be in LOW state, SDA in HIGH state (the state on exiting)
static uchar I2cWriteByte(uchar b) {
 uchar i=8;
 do{
  if (b&0x80) SDAH(); else SDAL();	// I²C transfers MSB first
  SCLP();
  b<<=1;
 }while (--i);
 SDAH();
 return SCLP();
}

// Read a byte from I²C and send the status bit <q>
// SCL must be in LOW state, SDA in HIGH state (the state on exiting)
static uchar I2cReadByte(uchar q) {
 uchar i=8,b=0;
 do{
  b<<=1;
  if (SCLP()) b|=1;		// read MSB first
 }while (--i);
 if (!q) SDAL();
 SCLP();
 SDAH();			// release SDA
 return b;
}

// assumes SDA in HIGH state and SCL in any state
// releases I²C with SDA = HIGH and SCL = LOW
static void I2cStart(void) {
 wait_us(3);		// wait 1 µs
 SCLH();		// necessary if I²C bus is in arbited state
 wait_us(3);		// wait 5 µs
 SDAL();		// do SDA H->L transition while SCL is high
 wait_us(3);		// wait 5 µs
 SCLL();		// initialize SCL
 wait_us(3);		// wait 5 µs
 SDAH();		// initialize SDA
}

// assumes I²C with SDA = HIGH and SCL = LOW
// releases I²C with SDA = SCL = HIGH
static void I2cStop(void) {
// char w=0;
 SDAL();
 wait_us(3);		// wait 5 µs
 SCLH();
 wait_us(3);		// wait 5 µs
// do if (SCLR()) break; while(--w);
 SDAH();		// do SDA L->H transition while SCL is high
// do if (SDAR()) break; while(--w);
}

/**********************************************************
 * Access glove's internal I²C EEPROM 24LC02B (or bridge) *
 **********************************************************/

// Read serial EEPROM from start (max. 255 Bytes)
// Returns number of successfully read bytes (always 0 or <buflen>)
static uchar ReadEeprom(uchar addr, uchar*buf, uchar buflen) {
 uchar ret=0;
 I2cStart();
 if (!I2cWriteByte(0xA0)	// EEPROM I²C write address
 && !I2cWriteByte(addr)) {	// EEPROM address
  I2cStart();
  if (!I2cWriteByte(0xA1)) {	// EEPROM I²C read address
   while(++ret<=buflen) {
    *buf++=I2cReadByte(buflen==ret);
   }
  }
 }
 I2cStop();
 return ret;
}

register uchar sig_x1 asm("r6");
register uchar sig_x2 asm("r7");	// signature bytes for the connectors
register uchar SwapLR asm("r2");	// gloves swapped

// retrieve left-glove ('L') or right-glove ('R') signature
// Any other value means no or unknown signature ("NO GLOVE" report)
uchar GetSig(void) {
 uchar ret=0;
 if (!SDAR()) ret='L';		// SubD bridge 6-9 (self-made left glove detect)
 else if (!SCLR()) ret='R';	// SubD bridge 6-8 (self-made right glove detect)
 else ReadEeprom(0,&ret,1);	// read first byte of signature EEPROM
 return ret;
}

// read in signature bytes from the connected gloves (first byte of I²C EEPROM)
// To be called repeatedly to detect connecting/disconnecting a glove
static void ReadSig(void) {
 SwapLR=CurI2c=0;	// assume no signature
 sig_x1=GetSig();
 CurI2c++;
 sig_x2=GetSig();
 if ((sig_x1=='R' && sig_x2!='R')
  || (sig_x2=='L' && sig_x1!='L')) SwapLR++;	// gloves swapped
}

/*********************
 * USB and CDC stuff *
 *********************/

enum {
 SEND_ENCAPSULATED_COMMAND = 0,
 GET_ENCAPSULATED_RESPONSE,
 SET_COMM_FEATURE,
 GET_COMM_FEATURE,
 CLEAR_COMM_FEATURE,
 SET_LINE_CODING = 0x20,
 GET_LINE_CODING,
 SET_CONTROL_LINE_STATE,
 SEND_BREAK};

PROGMEM char usbDescriptorConfiguration[] = {   /* USB configuration descriptor */
 9,	/* sizeof(usbDescrConfig): length of descriptor in bytes */
 USBDESCR_CONFIG,	/* descriptor type */
 67,0,	/* total length of data returned (including inlined descriptors) */
 2,	/* number of interfaces in this configuration */
 1,	/* index of this configuration */
 0,	/* configuration name string index */
 USBATTR_BUSPOWER,	/* attributes */
 20/2,	/* max USB current in 2mA units */

/* interface descriptor */
 9,	/* sizeof(usbDescrInterface): length of descriptor in bytes */
 USBDESCR_INTERFACE, /* descriptor type */
 0,	/* index of this interface */
 0,	/* alternate setting for this interface */
 1,	/* endpoints excl 0: number of endpoint descriptors to follow */
 2,	/* control interface class: CDC */
 2,	/* control interface subclass: Abstract (Modem) */
 1,	/* control interface protocol: AT commands */
 0,	/* string index for interface */

/* CDC Class-Specific descriptor */
 5,	/* sizeof(usbDescrCDC_HeaderFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 0,	/* header functional descriptor */
 0x10,0x01,

 4,	/* sizeof(usbDescrCDC_AcmFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 2,	/* abstract control management functional descriptor */
 0x02,	/* SET_LINE_CODING,    GET_LINE_CODING, SET_CONTROL_LINE_STATE    */

 5,	/* sizeof(usbDescrCDC_UnionFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 6,	/* union functional descriptor */
 0,	/* CDC_COMM_INTF_ID */
 1,	/* CDC_DATA_INTF_ID */

 5,	/* sizeof(usbDescrCDC_CallMgtFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 1,	/* call management functional descriptor */
 3,	/* allow management on data interface, handles call management by itself */
 1,	/* CDC_DATA_INTF_ID */

/* Endpoint Descriptor */
 7,	/* sizeof(usbDescrEndpoint) */
 USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
 0x83,	/* IN endpoint number 3 */
 0x03,	/* attrib: Interrupt endpoint */
 8,0,	/* maximum packet size */
 2,	/* polling interval in ms */

/* Second interface Descriptor */
 9,	/* sizeof(usbDescrInterface): length of descriptor in bytes */
 USBDESCR_INTERFACE,           /* descriptor type */
 1,	/* index of this interface */
 0,	/* alternate setting for this interface */
 2,	/* endpoints excl 0: number of endpoint descriptors to follow */
 0x0A,	/* Data Interface Class */
 0,	/* Data Interface Subclass */
 0,	/* Data Interface Class Protocol Codes */
 0,	/* string index for interface */

/* Endpoint Descriptor */
 7,           /* sizeof(usbDescrEndpoint) */
 USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
 0x01,	/* OUT endpoint number 1 */
 0x02,	/* attrib: Bulk endpoint */
 8,0,	/* maximum packet size 8->6 */
 0,

/* Endpoint Descriptor */
 7,           /* sizeof(usbDescrEndpoint) */
 USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
 0x81,	/* IN endpoint number 1 */
 0x02,	/* attrib: Bulk endpoint */
 8,0,	/* maximum packet size */
 0,
};



static uchar	requestType;
static uchar	modeBuffer[7];
static uchar	intr3Status;    /* used to control interrupt endpoint transmissions */
static uchar	lastLen;	// if 8, send an empty BULK packet

/***********************
 * SETUP data received *
 ***********************/
uchar usbFunctionSetup(uchar data[8]) {
 usbRequest_t *rq = (void*)data;

 if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {    /* class request type */
  if (rq->bRequest==GET_LINE_CODING || rq->bRequest==SET_LINE_CODING) {
   requestType = rq->bRequest;
   return 0xff;
/*    GET_LINE_CODING -> usbFunctionRead()    */
/*    SET_LINE_CODING -> usbFunctionWrite()    */
  }
  if (rq->bRequest == SET_CONTROL_LINE_STATE) {
/* Report serial state (carrier detect). On several Unix platforms,
 * tty devices can only be opened when carrier detect is set.
 */
   if (!intr3Status) intr3Status=2;
  }

/*  Prepare bulk-in endpoint to respond to early termination   */
  if ((rq->bmRequestType & USBRQ_DIR_MASK) == USBRQ_DIR_HOST_TO_DEVICE)
   lastLen=8;	// send an empty packet
 }
 return 0;
}

/***********************
 * IN transfer for EP0 *
 ***********************/
uchar usbFunctionRead(uchar *data, uchar len) {
 if (requestType == GET_LINE_CODING) {
  memcpy(data,modeBuffer,7);
  return 7;
 }
 return 0;	/* error -> terminate transfer */
}


/************************
 * OUT transfer for EP0 *
 ************************/
uchar usbFunctionWrite(uchar *data, uchar len) {
 if (requestType == SET_LINE_CODING) {
  memcpy(modeBuffer,data,7);
  return 1;
 }
 return 1;	/* error -> accept everything until end */
}

/*****************************************
 * Simple ring buffer for EP1IN transfer *
 *****************************************/
typedef struct {
 uchar rp,wp;
 uchar buf[256];
}ringbuf_t;

ringbuf_t Ringbuf;

// Put datagram (message) into <Ringbuf>, returns !=0 when _not_ OK
uchar Ringbuf_Put(const uchar*data,uchar len) {
 if (!len) return len;
 if (len>(uchar)(Ringbuf.rp-Ringbuf.wp-1)) return len;	// message does not fit
 do{
  Ringbuf.buf[Ringbuf.wp++]=*data++;
 }while (--len);
 return len;		// message does fit
}

// Retrieve contiguous data from <Ringbuf>
// Returns number of bytes read
uchar Ringbuf_Get(uchar*data, uchar len) {
 uchar ret=0;
 for (;Ringbuf.rp!=Ringbuf.wp && len; len--) {
  *data++=Ringbuf.buf[Ringbuf.rp++];
  ret++;
 }
 return ret;
}

/************************************
 * OUT transfer for EP1 (Bulk data) *
 ************************************/
static uchar rbuf;		// memory for command byte
static uchar tbuf[60];		// multi-function buffer
static uchar DefPrefixByte;	// 0x80 or 0x81

void usbFunctionWriteOut(uchar *data, uchar len) {
 uchar c=rbuf,d;
 if (!len) return;
 Led_Start(150);		// fast flashing (3 Hz)
 do{
  d=*data++;
  if (!c) {			// no command given
   if (' '<d && d<0x7F) c=d;	// ignore invisible characters as command
  }else{			// command available
   uchar *p=tbuf;
   *p++=0x82;
   if (c=='C') {
    if (d=='L' || d=='R') {
     uchar l;
     ReadSig();			// Check for gloves now
     if (d==sig_x1) CurI2c=0;
     else if (d==sig_x2) CurI2c=1;
     else{		// no EEPROM
      *p++=d;
      strcpy_P((char*)p,PSTR(" NO GLOVE"));
      p+=9;
      goto Out;
     }
     l=ReadEeprom(0,p,30/*sizeof(tbuf)-2*/);
     if (l) do{			// EEPROM data available
      uchar c=*p++;
      if (c<0x20 || c>=0x7F) {	// scan data until invalid character occurs
       p--;
       break;
      }
     }while(--l); else{		// no EEPROM, bridge only
      *p++=d;
      strcpy_P((char*)p,PSTR(" GLOVE"));
      p+=6;
     }
     goto Out;
    }
    if (d=='P') {
     strcpy_P((char*)p,
       PSTR("V3.0B PINCH by Fakespace Inc. 1995 - haftmann#software"));
     p+=54;
     goto Out;
    }
    goto OutError;
   }
   if (c=='G' && (d=='0' || d=='1')) {	// Report ground connection on/off
    RepGnd=d-'0';
    *p++=d;
    goto Out;
   }
   if (c=='T' && (d=='0' || d=='1')) {	// Report timestamp on/off
    DefPrefixByte=d-'0'+0x80;
    *p++=d;
    goto Out;
   }
OutError:
   *p++='?';
Out:
   *p++=0x8F;
   Ringbuf_Put(tbuf,p-tbuf);
   c=0;			// delete pending command
  }
 }while(--len);
 rbuf=c;	// save valid command byte, expecting second byte
}

/*************************************************
 * Making a connect/disconnect message for EP1IN *
 *************************************************/
static unsigned Timestamp;	// 14bit non-rollover counter

static void SendConnectMessage(void) {
 uchar cnt=Sorted.N;		// number of WORDs
 uchar pre=DefPrefixByte;	// Message introducer
 uchar *p=tbuf;			// pointer to universal buffer
 *p++=pre;
 if (cnt) {
  if (SwapLR) {			// copy while byte swapping
   uchar *s=(uchar*)&Sorted.C+2;
   do{
    *p++=*--s;			// AVR optimized
    *p++=*--s;
    s+=4;
   }while(--cnt);
  }else{
   cnt<<=1;
   memcpy(p,&Sorted.C,cnt);	// copy directly
   p+=cnt;
  }
 }
 if (pre==0x81) {
  *p++=Timestamp&0x7F;
  *p++=Timestamp>>7;
 }
 *p++=0x8F;			// Message terminator
 if (!Ringbuf_Put(tbuf,p-tbuf)) Timestamp=0;
 Led_Start(100);		// Faster flashing (5 Hz)
}

/********************************
 * Initialization and main loop *
 ********************************/
EEMEM uchar eeTstate, eeGstate;	// EEPROM byte locations
 
static void hardwareInit(void) {
 PORTB=PORTC=0x3F;	// weak pullups everywhere (except USB data lines)
 PORTD=0xF3;
 DDRC=0x20;		// LED as OUTPUT
 DefPrefixByte=0x80;	// answer without time stamp
 ReadSig();
 if (eeprom_read_byte(&eeTstate)==0x01) DefPrefixByte=0x81;
 if (eeprom_read_byte(&eeGstate)==0x01) RepGnd=0x01;
}

// Updates an ATmega-internal EEPROM location when different
static void ActualizeEepromByte(uchar *ee_addr, uchar b) {
 if (eeprom_read_byte(ee_addr)==b) return;
 EEDR=b;
 cli();
 EECR|=1<<EEMWE;
 EECR|=1<<EEWE;		// don't wait here!
 sei();
}

// Updates all internal EEPROM locations (here: "T" and "G" states)
// To be called repeatedly in mainloop - does not block
static void ActualizeEeprom(void) {
 if (!eeprom_is_ready()) return;
 ActualizeEepromByte(&eeTstate,DefPrefixByte-0x80);
 if (!eeprom_is_ready()) return;
 ActualizeEepromByte(&eeGstate,RepGnd);
}

int __attribute__((noreturn)) main(void) {
 uchar SofCmp=0;
 uchar mcucsr=MCUCSR;
 MCUCSR=0;
 ACSR|=0x80;		// disable analog comparator and save 70µA
 wdt_disable();		// watchdog survives RESET: disable it
 if (mcucsr & (1 << WDRF)) {
  if (USBIN & USBMASK) {	// no SE0 state?
   GICR=0x80;
   MCUCR=0xA0;		// SLEEP_MODE_PWR_DOWN and level interrupt for INT1
   sei();		// must be enabled for wakeup
   sleep_cpu();		// stop crystal and let INT1 do the next wakeup
   cli();
  }
 }
 MCUCR=0x80;		// SLEEP_MODE_STANDBY
 wdt_enable(WDTO_15MS);	// no watchdog fuse here due to boot loader
 usbInit();
 sei();
 hardwareInit();

 for(;;){	// main event loop (infinite, possibly broken by watchdog)
  uchar t,d;
  if (USBIN&USBMASK) sleep_cpu();	// sleep, except at SE0, until SOF
  t=usbSofCount;	// atomic access to volatile variable
  d=t-SofCmp;		// time difference [ms]
  if (d || (USBIN&USBMASK)) {
   wdt_reset();
  }
  usbPoll();
  if (d) {		// After 1 ms (or maybe more)
   SofCmp=t;
   Timestamp+=d;
   if (Timestamp&0x4000) Timestamp=0x3FFF; // increment without 14bit rollover
   do Led_On1ms(); while(--d);
   FindConnGroups();
   if (StableConnGroups() && SortConnGroups()) {
    if (Timestamp>=10000) ReadSig();	// read signature after long inactivity
    SendConnectMessage();
   }
  }
        /*    device -> host    */
  if (usbInterruptIsReady()) {
   uchar len;
   len=Ringbuf_Get(tbuf,8);
   if (len || lastLen&8 ) {
    usbSetInterrupt(tbuf,len);
/* let send an empty block after last data block to indicate transfer end */
    lastLen=len;
   }
  }

        /* We need to report rx and tx carrier after open attempt */
  if( usbInterruptIsReady3() && intr3Status ) {
   static uchar serialStateNotification[10] = {
     0xa1, 0x20, 0, 0, 0, 0, 2, 0, 3, 0};

   if (intr3Status==2) usbSetInterrupt3(serialStateNotification, 8);
   else                usbSetInterrupt3(serialStateNotification+8, 2);
   intr3Status--;
  }
  ActualizeEeprom();
 }
}

/* Future features:
 * Remote Wakeup: Contacting any (or specific) finger
   will wake-up the connected PC/Laptop. Requires Watchdog use or ATmega88
 * Touching a fixed TTL contact area: decoding info (as such as
   "You have touched the knob 42 with the right hand middle finger.")
 * HID device (to be more conform to USB standard
   that forbids BULK for low-speed - less complicated Linux support)
 */
Detected encoding: UTF-80