Quelltext /~heha/basteln/PC/FunkUsb/FunkUsb.zip/FunkUsb/joy/FunkUsb.c

/* Funkuhr-Empfänger, Umsetzung auf 1-Knopf-Joystick (anstatt COM-Port)
 + Kein Problem mit Windows 98/Me oder Vista/7 oder 64 Bits oder Linux.
 + Keine Treiber erforderlich
 + Die Funktion lässt sich bequem (ohne Hilfsprogramm)
   in der Systemsteuerung → Gamecontroller beobachten
 + Passt auch in einen ATtiny25 (2 KByte Flash)
 * TabSize = 8, Charset = UTF-8, LineEnds=LF
 121118	Erste Ausgabe
+130401	Konfigurierbare E/A-Anschlüsse und Invertierungen
	Nur USB D- muss an PB2 bleiben für INT0.
	Die gedimmte LED gibt's nur an PB0 oder PB1.
+130407	1 Hebel (X) dazu für 16-bit-Zeitstempel in ms
+150131	1 Schieber dazu für 7-Bit-Zeitverzug des Report-Lesens in ms
 */

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

#include "usbdrv.c"

/* Hardware:
PB0 (5)	LED (low-aktiv) = OC0A (Helligkeit per PWM)
PB1 (6)	USB D+
PB2 (7)	USB D- = INT0 (Interrupts auch bei SOF)
PB3 (2)	DCF77-Zeitzeichensignal, LOW = Trägerabsenkung, wie RxD
PB4 (3)	Freigabe des Funkempfangsmoduls, LOW = aktiv
*/
#define LED_BIT 0	// LED-Anschluss
#define LED_INV 1	// 1 = Low-aktiv
#define SIG_BIT 3	// DCF77-Trägerabsenkung
#define SIG_INV 1	// 1 = Low-aktiv
#define SIG_PU  0	// 1 = PullUp erforderlich
#define ENA_BIT 4	// Empfänger-Freigabe
#define ENA_INV 1	// 1 = Low-aktiv

static struct {
 uchar key_sl;		// Bit 0 = Trägerabsenkung
			// Bit 7:1 = Millisekunden bis zum GetReport
 uint16_t axis;		// Zeitstempel als Joystick-Achse
}InputReport;

static uchar LevelChange;	// 1 wenn Pegelwechsel aufgetreten
static uint16_t TimeStamp;

ISR(PCINT0_vect, ISR_NOBLOCK) {	// Pegelwechsel-Interrupt (vom Empfänger)
 LevelChange = 1;
 if (SIG_INV ? !(PINB&1<<SIG_BIT) : PINB&1<<SIG_BIT) {
#if LED_BIT==0
  OCR0A  = 0xFF;	// LED ein
#else
#if LED_BIT==1
  OCR0B  = 0xFF;
#else
#if LED_INV
  PORTB&=~1<<LED_BIT;
#else
  PORTB|=1<<LED_BIT;
#endif
#endif
#endif
  InputReport.key_sl=1;	// Knopf „drücken“
 }else{			// High (voller Träger)
#if LED_BIT==0
  OCR0A  = 0x30;	// LED dunkel (nicht aus)
#else
#if LED_BIT==1
  OCR0B  = 0x30;
#else
#if LED_INV
  PORTB|=1<<LED_BIT;	// LED aus
#else
  PORTB&=~1<<LED_BIT;
#endif
#endif
#endif
  InputReport.key_sl=0;
 }
 InputReport.axis=TimeStamp;
}

/**************************
 * HID-Report-Beschreiber *
 **************************/
#define W(x) (x)&0xFF,(x)>>8
#define D(x) W(x&0xFFFF),W(x>>16)

PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
 0x05, 0x01,		// G Usage Page (Generic Desktop)
 0x09, 0x04,		// L Usage (Joystick)
 0xA1, 1,		// M Collection (Application)
 0x15, 0,		//  G Logical Minimum (0)
 0x25, 1,		//  G Logical Maximum (1)
 0x75, 1,		//  G Report Size (1)
 0x95, 1,		//  G Report Count (1)
 0x0B, D(0x00090001L),	//  L Usage (Buttons:Button 1)
 0x81, 0x02,		//  M Input (Var)	// Trägerabsenkung
 0x25, 127,		//  G Logical Maximum (127)
 0x75, 0x07,		//  G Report Size (7)
 0x09, 0x36,		//  L Usage (Slider)
 0x81, 0x02,		//  M Input (Var)	// ms bis zum GetReport
 0x27, D(0x0000FFFFL),	//  G Logical Maximum (65535)
 0x75, 16,		//  G Report Size (16)
 0x09, 0x30,		//  L Usage (X)
 0x81, 0x02,		//  M Input (Var)	// Zeitstempel
 0xC0};			// M End Collection


/***************************
 * Wenn SETUP-Daten kommen *
 ***************************/
uchar usbFunctionSetup(uchar data[8]) {
 usbRequest_t *rq = (void*)data;
/* class request type (x01x xxxx) */
 if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
  if (rq->bRequest == USBRQ_HID_GET_REPORT) {
   if (rq->wValue.bytes[1] == 1) {		// Input
    usbMsgPtr = (uchar*)&InputReport;
    return sizeof InputReport;
   }
  }
 }
 return 0;
}

/*******************
 * Initialisierung *
 *******************/
 
static void hardwareInit(void) {
 PRR    = 0x0B;		// alles aus, außer Timer0
#if LED_BIT==0
 TCCR0A = 0x83 | LED_INV<<6;	// Schnelle PWM auf OC0A (LED-Helligkeit)
 OCR0A  = 0x30;		// LED dunkel (nicht aus)
#endif
#if LED_BIT==1
 TCCR0A = 0x23 | LED_INV<<4;	// PWM (invertiert oder nicht)
 OCR0B  = 0x30;		// LED dunkel (nicht aus)
#endif
 TCCR0B = 0x02;		// Achtel-Taktfrequenz
 DDRB   = 1<<LED_BIT | 1<<ENA_BIT;	// LED und Freigabeausgang
 PORTB  = SIG_PU<<SIG_BIT | !LED_INV<<LED_BIT | !ENA_INV<<ENA_BIT;
			// Pull-Up für SIG, LED und Freigabe aktivieren
 PCMSK  = 1<<SIG_BIT;	// Pegelwechselinterrupt (nur) an diesem Eingang
 GIMSK  = 0x60;		// INT0 sowie Pegelwechsel-Interrupt
}

// Diese Routine beachtet Grenzen von OSCCAL und die zwei Bereiche
// der ATtinyX5 nicht! Bis jetzt geht's trotzdem.
static void tuneOscillator(void) {
 static uchar lastTick;
#define SOLL 14	// usbSofDiff sollte 16500/8%256 = 14.5 sein
#define MABW 7	// Abweichung kleiner ±6 ist OK und wird ignoriert
 if (GPIOR0==1) {		// Ohne Überlauf?
  schar t = GPIOR1-lastTick-SOLL;	// zentriere Ergebnis um die Null
  if (t<-MABW) OSCCAL++;	// schneller machen
  if (t> MABW) OSCCAL--;	// langsamer machen
 }
 cli();
 TimeStamp+=GPIOR0;		// erhöhen
 InputReport.key_sl+=GPIOR0<<1; // Alter des Reports zählen (Bit 0 belassen)
 lastTick=GPIOR1;
 sei();
 GPIOR0=0;			// noch im cli()
}

/*****************
 * Hauptprogramm *
 *****************/
int main(void) __attribute__((noreturn));
int main(void) {
 uchar mcusr = MCUSR;
 MCUSR = 0;
 ACSR |= 0x80;		// Analogvergleicher abschalten: - 70 µA
 WDTCR = 0x18;		// Watchdog überlebt RESET: abschalten!
 WDTCR = 0;
 GPIOR0= 0;
 if (mcusr & (1 << WDRF)) {	// Wenn die Reset-Ursache der Watchdog war...
  if (USBIN & USBMASK) {	// kein SE0-State?
   GIMSK = 0x40;
   MCUCR = 0x30;	// SLEEP_MODE_PWR_DOWN und Pegelinterrupt an INT0
#if ENA_INV
   PORTB |= 1<<ENA_BIT;	// Funkuhr-Freigabe: ausschalten!
#else
   PORTB &=~(1<<ENA_BIT);
#endif
   sei();		// Interrupts müssen für WakeUp eingeschaltet sein
   sleep_cpu();		// Oszillator anhalten, warten auf INT0-LOW-Pegel
   cli();		// INT0 aufgetreten (kann nur USB-RESET == SE0 sein)
  }
 }
 MCUCR=0x20;		// SLEEP_MODE_STANDBY
 hardwareInit();
 usbInit();
 while (!(USBIN&USBMASK));	// SE0 abwarten (?)
 WDTCR  = 0x08;		// Watchdog mit kürzestmöglicher Zeit (16 ms) aktivieren
 sei();

 for(;;){	// Endlos-Hauptschleife, wird höchstens vom Watchdog abgebrochen
  sleep_cpu();	// CPU schläft bis SOF (Ständige ISR-Abarbeitung während SE0!)
  wdt_reset();
  usbPoll();
  if (GPIOR0) tuneOscillator();
  if (usbInterruptIsReady() && LevelChange) {	// Pegelwechsel aufgetreten?
   usbSetInterrupt((uchar*)&InputReport,sizeof InputReport);
   LevelChange=0;
  }
 }
}
Vorgefundene Kodierung: UTF-80