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