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-8 | 0
|