Source file: /~heha/basteln/PC/FunkUsb/FunkUsb.zip/FunkUsb/joy/FunkUsb.c

     1  /* Funkuhr-Empfänger, Umsetzung auf 1-Knopf-Joystick (anstatt COM-Port)
     2   + Kein Problem mit Windows 98/Me oder Vista/7 oder 64 Bits oder Linux.
     3   + Keine Treiber erforderlich
     4   + Die Funktion lässt sich bequem (ohne Hilfsprogramm)
     5     in der Systemsteuerung → Gamecontroller beobachten
     6   + Passt auch in einen ATtiny25 (2 KByte Flash)
     7   * TabSize = 8, Charset = UTF-8, LineEnds=LF
     8   121118	Erste Ausgabe
     9  +130401	Konfigurierbare E/A-Anschlüsse und Invertierungen
    10  	Nur USB D- muss an PB2 bleiben für INT0.
    11  	Die gedimmte LED gibt's nur an PB0 oder PB1.
    12  +130407	1 Hebel (X) dazu für 16-bit-Zeitstempel in ms
    13  +150131	1 Schieber dazu für 7-Bit-Zeitverzug des Report-Lesens in ms
    14   */
    15  
    16  #include <avr/io.h>
    17  #include <avr/interrupt.h>
    18  #include <avr/pgmspace.h>
    19  #include <avr/wdt.h>
    20  #include <avr/sleep.h>
    21  
    22  #include "usbdrv.c"
    23  
    24  /* Hardware:
    25  PB0 (5)	LED (low-aktiv) = OC0A (Helligkeit per PWM)
    26  PB1 (6)	USB D+
    27  PB2 (7)	USB D- = INT0 (Interrupts auch bei SOF)
    28  PB3 (2)	DCF77-Zeitzeichensignal, LOW = Trägerabsenkung, wie RxD
    29  PB4 (3)	Freigabe des Funkempfangsmoduls, LOW = aktiv
    30  */
    31  #define LED_BIT 0	// LED-Anschluss
    32  #define LED_INV 1	// 1 = Low-aktiv
    33  #define SIG_BIT 3	// DCF77-Trägerabsenkung
    34  #define SIG_INV 1	// 1 = Low-aktiv
    35  #define SIG_PU  0	// 1 = PullUp erforderlich
    36  #define ENA_BIT 4	// Empfänger-Freigabe
    37  #define ENA_INV 1	// 1 = Low-aktiv
    38  
    39  static struct {
    40   uchar key_sl;		// Bit 0 = Trägerabsenkung
    41  			// Bit 7:1 = Millisekunden bis zum GetReport
    42   uint16_t axis;		// Zeitstempel als Joystick-Achse
    43  }InputReport;
    44  
    45  static uchar LevelChange;	// 1 wenn Pegelwechsel aufgetreten
    46  static uint16_t TimeStamp;
    47  
    48  ISR(PCINT0_vect, ISR_NOBLOCK) {	// Pegelwechsel-Interrupt (vom Empfänger)
    49   LevelChange = 1;
    50   if (SIG_INV ? !(PINB&1<<SIG_BIT) : PINB&1<<SIG_BIT) {
    51  #if LED_BIT==0
    52    OCR0A  = 0xFF;	// LED ein
    53  #else
    54  #if LED_BIT==1
    55    OCR0B  = 0xFF;
    56  #else
    57  #if LED_INV
    58    PORTB&=~1<<LED_BIT;
    59  #else
    60    PORTB|=1<<LED_BIT;
    61  #endif
    62  #endif
    63  #endif
    64    InputReport.key_sl=1;	// Knopf „drücken“
    65   }else{			// High (voller Träger)
    66  #if LED_BIT==0
    67    OCR0A  = 0x30;	// LED dunkel (nicht aus)
    68  #else
    69  #if LED_BIT==1
    70    OCR0B  = 0x30;
    71  #else
    72  #if LED_INV
    73    PORTB|=1<<LED_BIT;	// LED aus
    74  #else
    75    PORTB&=~1<<LED_BIT;
    76  #endif
    77  #endif
    78  #endif
    79    InputReport.key_sl=0;
    80   }
    81   InputReport.axis=TimeStamp;
    82  }
    83  
    84  /**************************
    85   * HID-Report-Beschreiber *
    86   **************************/
    87  #define W(x) (x)&0xFF,(x)>>8
    88  #define D(x) W(x&0xFFFF),W(x>>16)
    89  
    90  PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
    91   0x05, 0x01,		// G Usage Page (Generic Desktop)
    92   0x09, 0x04,		// L Usage (Joystick)
    93   0xA1, 1,		// M Collection (Application)
    94   0x15, 0,		//  G Logical Minimum (0)
    95   0x25, 1,		//  G Logical Maximum (1)
    96   0x75, 1,		//  G Report Size (1)
    97   0x95, 1,		//  G Report Count (1)
    98   0x0B, D(0x00090001L),	//  L Usage (Buttons:Button 1)
    99   0x81, 0x02,		//  M Input (Var)	// Trägerabsenkung
   100   0x25, 127,		//  G Logical Maximum (127)
   101   0x75, 0x07,		//  G Report Size (7)
   102   0x09, 0x36,		//  L Usage (Slider)
   103   0x81, 0x02,		//  M Input (Var)	// ms bis zum GetReport
   104   0x27, D(0x0000FFFFL),	//  G Logical Maximum (65535)
   105   0x75, 16,		//  G Report Size (16)
   106   0x09, 0x30,		//  L Usage (X)
   107   0x81, 0x02,		//  M Input (Var)	// Zeitstempel
   108   0xC0};			// M End Collection
   109  
   110  
   111  /***************************
   112   * Wenn SETUP-Daten kommen *
   113   ***************************/
   114  uchar usbFunctionSetup(uchar data[8]) {
   115   usbRequest_t *rq = (void*)data;
   116  /* class request type (x01x xxxx) */
   117   if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
   118    if (rq->bRequest == USBRQ_HID_GET_REPORT) {
   119     if (rq->wValue.bytes[1] == 1) {		// Input
   120      usbMsgPtr = (uchar*)&InputReport;
   121      return sizeof InputReport;
   122     }
   123    }
   124   }
   125   return 0;
   126  }
   127  
   128  /*******************
   129   * Initialisierung *
   130   *******************/
   131   
   132  static void hardwareInit(void) {
   133   PRR    = 0x0B;		// alles aus, außer Timer0
   134  #if LED_BIT==0
   135   TCCR0A = 0x83 | LED_INV<<6;	// Schnelle PWM auf OC0A (LED-Helligkeit)
   136   OCR0A  = 0x30;		// LED dunkel (nicht aus)
   137  #endif
   138  #if LED_BIT==1
   139   TCCR0A = 0x23 | LED_INV<<4;	// PWM (invertiert oder nicht)
   140   OCR0B  = 0x30;		// LED dunkel (nicht aus)
   141  #endif
   142   TCCR0B = 0x02;		// Achtel-Taktfrequenz
   143   DDRB   = 1<<LED_BIT | 1<<ENA_BIT;	// LED und Freigabeausgang
   144   PORTB  = SIG_PU<<SIG_BIT | !LED_INV<<LED_BIT | !ENA_INV<<ENA_BIT;
   145  			// Pull-Up für SIG, LED und Freigabe aktivieren
   146   PCMSK  = 1<<SIG_BIT;	// Pegelwechselinterrupt (nur) an diesem Eingang
   147   GIMSK  = 0x60;		// INT0 sowie Pegelwechsel-Interrupt
   148  }
   149  
   150  // Diese Routine beachtet Grenzen von OSCCAL und die zwei Bereiche
   151  // der ATtinyX5 nicht! Bis jetzt geht's trotzdem.
   152  static void tuneOscillator(void) {
   153   static uchar lastTick;
   154  #define SOLL 14	// usbSofDiff sollte 16500/8%256 = 14.5 sein
   155  #define MABW 7	// Abweichung kleiner ±6 ist OK und wird ignoriert
   156   if (GPIOR0==1) {		// Ohne Überlauf?
   157    schar t = GPIOR1-lastTick-SOLL;	// zentriere Ergebnis um die Null
   158    if (t<-MABW) OSCCAL++;	// schneller machen
   159    if (t> MABW) OSCCAL--;	// langsamer machen
   160   }
   161   cli();
   162   TimeStamp+=GPIOR0;		// erhöhen
   163   InputReport.key_sl+=GPIOR0<<1; // Alter des Reports zählen (Bit 0 belassen)
   164   lastTick=GPIOR1;
   165   sei();
   166   GPIOR0=0;			// noch im cli()
   167  }
   168  
   169  /*****************
   170   * Hauptprogramm *
   171   *****************/
   172  int main(void) __attribute__((noreturn));
   173  int main(void) {
   174   uchar mcusr = MCUSR;
   175   MCUSR = 0;
   176   ACSR |= 0x80;		// Analogvergleicher abschalten: - 70 µA
   177   WDTCR = 0x18;		// Watchdog überlebt RESET: abschalten!
   178   WDTCR = 0;
   179   GPIOR0= 0;
   180   if (mcusr & (1 << WDRF)) {	// Wenn die Reset-Ursache der Watchdog war...
   181    if (USBIN & USBMASK) {	// kein SE0-State?
   182     GIMSK = 0x40;
   183     MCUCR = 0x30;	// SLEEP_MODE_PWR_DOWN und Pegelinterrupt an INT0
   184  #if ENA_INV
   185     PORTB |= 1<<ENA_BIT;	// Funkuhr-Freigabe: ausschalten!
   186  #else
   187     PORTB &=~(1<<ENA_BIT);
   188  #endif
   189     sei();		// Interrupts müssen für WakeUp eingeschaltet sein
   190     sleep_cpu();		// Oszillator anhalten, warten auf INT0-LOW-Pegel
   191     cli();		// INT0 aufgetreten (kann nur USB-RESET == SE0 sein)
   192    }
   193   }
   194   MCUCR=0x20;		// SLEEP_MODE_STANDBY
   195   hardwareInit();
   196   usbInit();
   197   while (!(USBIN&USBMASK));	// SE0 abwarten (?)
   198   WDTCR  = 0x08;		// Watchdog mit kürzestmöglicher Zeit (16 ms) aktivieren
   199   sei();
   200  
   201   for(;;){	// Endlos-Hauptschleife, wird höchstens vom Watchdog abgebrochen
   202    sleep_cpu();	// CPU schläft bis SOF (Ständige ISR-Abarbeitung während SE0!)
   203    wdt_reset();
   204    usbPoll();
   205    if (GPIOR0) tuneOscillator();
   206    if (usbInterruptIsReady() && LevelChange) {	// Pegelwechsel aufgetreten?
   207     usbSetInterrupt((uchar*)&InputReport,sizeof InputReport);
   208     LevelChange=0;
   209    }
   210   }
   211  }
   212  
Detected encoding: UTF-80