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