/* 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 <avr/signature.h>
#include <avr/fuse.h>
#include "usbdrv.c"
FUSES = {0xE1,0xD5,0xFF};
/* 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)
const 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;
}
// avoid warning about declared but not defined but unused function
// (linker will kick out this function)
uchar usbFunctionDescriptor(struct usbRequest*rq) {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 __attribute__((OS_main)) 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;
}
}
}
Detected encoding: UTF-8 | 0
|