Source file: /~heha/basteln/PC/FunkUsb/FunkUsb.zip/FunkUsb/ve/FunkUsb.c

/* Funkuhr-Empfänger, Umsetzung auf HID-Gerät
 + Kein Problem mit Windows 98/Me oder Vista/7 oder 64 Bits oder Linux.
 + Keine Treiber erforderlich
 * „Vorlaufempfänger“, vollständige Dekodierung und Wetter-Speicherung
 * TabSize = 8, Charset = UTF-8, LineEnds=LF
 */

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

#include "usbdrv.h"
#include "FunkUsb.h"

struct JoyReport JoyReport;
struct TimeReport TimeReport;
struct SetupReport SetupReport;
struct StatusReport StatusReport;
struct HistoryReport HistoryReport;
struct WetterReport WetterReport;

//GPIOR0: Diverse Bits
	// Bit 0: Joystick-Report abschicken
	// Bit 1: Status-Report abschicken
	// Bit 2: Flanke für dcf77Poll
	// Bit 3: Sekunden-Tick (für A/D-Wandler)
	// Bit 5: SOF aufgetreten
	// Bit 6: Dirty-Flag für Flash-Sektor
	// Bit 7: IoFlash (SETUP-Datenphase)
//GPIOR1: frei (dafür ist OCR0B "verbraten")
//GPIOR2: frei

WORD T0Capture;
WORD lastStart;
WORD T0NT;	// Nachteiler für Timer0

static void Capture(void) {
 if (CLKPR&8) *(BYTE*)&T0NT = TCNT0;	// Low-Teil "korrigieren"
 T0Capture = T0NT;
}

ISR(PCINT0_vect) {	// Pegelwechsel-Interrupt (vom Empfänger)
 GIMSK=0x40;		// keine Rekursion durch nachfolgendes sei()!
 sei();
 GPIOR0|=1;
 GPIOR0|=2;
 GPIOR0|=4;
 Capture();
 if (SIG_INV ? !(PINB&(1<<SIG_BIT)) : PINB&(1<<SIG_BIT)) {
  JoyReport.joyKey=1;	// Knopf „drücken“
  StatusReport.someBits|=1;
 }else{
  JoyReport.joyKey=0;	// Knopf „lösen“
  StatusReport.someBits&=~1;
 }
 cli();
 GIMSK=0x60;
}

// Aufruf mit 8 kHz (bei Taktfrequenz 16,5 MHz),
// mit 31 Hz (bei Taktfrequenz 8 kHz wegen Taktteiler)
ISR(TIMER0_OVF_vect, ISR_NOBLOCK) {
 if (CLKPR&8) T0NT+=256;
 else T0NT++;
 if (!(GPIOR2&0x3F)) GPIOR0|=8;
}

ISR(TIMER0_COMPA_vect, ISR_NOBLOCK) {
}

// A/D-Wandler, 2-3 Durchläufe, alle 1 Sekunde
ISR(ADC_vect, ISR_NOBLOCK) {
 if (!(~ADMUX&0x0F)) {
  StatusReport.Chiptemperatur=ADC;
  GPIOR0|=2;	// Status-Report (#4) bereit, kann abgefragt werden
  // TODO: Akku beachten!
  ADCSRA=0;
  PRR=0x0B;	// ADC schlafen legen
 }else if (!(ADMUX&0x10)) {
  StatusReport.Akkuspannung=(WORD)((unsigned long)ADC*110>>8);
  ADMUX=0x8F;	// Chiptemperaturmessung
  ADCSRA|=0x40;	// als nächstes
 }else{
  WORD w=ADC;
  if (w<400) {	// kleinere Referenzspannung besser? (genauer: 1023*110/256)
   ADMUX&=~0x10;	// ja, wiederholen
  }else{
   StatusReport.Akkuspannung=w;
   ADMUX=0x8F;	// Chiptemperaturmessung
  }
  ADCSRA|=0x40;	// als nächstes
 }
}

void extern_flash_flush(void) {
// wenn Batteriebetrieb:
// Timer1 starten mit Vorteiler 2048
 flash_flush();
// T0NT
}

/**************************
 * 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)
 0x85, 1,		//  M Report ID (1)
 0x15, 0,		//  G Logical Minimum (0)
 0x25, 1,		//  G Logical Maximum (1)
 0x75, 1,		//  G Report Size (1)
 0x95, 1,		//  G Report Count (1)
 0x05, 0x09,		//  G Usage Page (Buttons)
 0x09, 0x01,		//  L Usage (Button 1)
 0x81, 0x02,		//  M Input (Var)	// Trägerabsenkung
 0x95, 0x07,		//  G Report Size (7)
 0x81, 0x01,		//  M Input (Const)		// Füllbits
 0xC0,			// M End Collection

 0x06, W(0xFF00),	// G Usage Page (Vendor Defined)
 0x09, 0x01,		// L Usage (Vendor Usage 1)
 0xA1, 1,		// M Collection (Application)
 0x85, 2,		//  M Report ID (2)
 0x15, 0,		//  G Logical Minimum (0)	// Keine genauere Beschreibung!
 0x26, W(255),		//  G Logical Maximum (255)
 0x75, 8,		//  G Report Size (8)
 0x95, 11,		//  G Report Count (9)
 0x09, 0x21,		//  L Usage (DCF77-Uhrzeit: ms4,s,min,h,d,wd,mon,y,tz)
 0xB1, 0x02,		//  M Feature (Var)

 0x95, 1,		//  G Report Count (1)
 0xA4,			//  G Push
 0x85, 3,		//  M Report ID (3)
 0x19, 0x31,		//  L Usage Minimum (Jahrhundert)
 0x29, 0x35,		//  L Usage Maximum (Akkutyp)
 0x95, 5,		//  G Report Count (5)
 0xB1, 0x02,		//  M Feature (Var)
 0x75, 16,		//  G Report Size (16)
 0x95, 2,		//  G Report Count (2)
 0x26, W(1023),		//  G Logical Maximum (1023)
 0x35, 0,		//  G Physical Minimum (0)
 0x46, W(5120),		//  G Physical Maximum (5,120 V)
 0x55, 4,		//  G Unit Exponent (4)
 0x67, D(0x00F0D121),	//  G Unit (Volt)
 0x19, 0x36,		//  L Usage Minimum (Ladeschluss)
 0x29, 0x37,		//  L Usage Maximum (Entladeschluss)
 0xB1, 0x02,		//  M Feature (Var)
 0xB4,			//  G Pop

 0xA4,			//  G Push
 0x85, 4,		//  M Report ID (4)
 0x25, 1,		//  G Logical Maximum (1)
 0x75, 1,		//  G Report Size (1)
 0x95, 8,		//  G Report Count (8)
 0x19, 0x40,		//  L Usage Minimum (Trägerabsenkung)
 0x29, 0x47,		//  L Usage Maximum (Neuer Tag)
 0x81, 0x02,		//  M Input (Var, Preferred State)
 0x75, 16,		//  G Report Size (16)
 0x95, 1,		//  G Report Count (1)
 0x26, W(1023),		//  G Logical Maximum (1023)
 0x35, 0,		//  G Physical Minimum (0)
 0x46, W(5120),		//  G Physical Maximum (5.120V)
 0x55, 4,		//  G Unit Exponent (4)
 0x67, D(0x00F0D121),	//  G Unit (Volt)
 0x0B, D(0x00840030),	//  L Usage (0x84/0x30, Spannung)
 0x81, 0x02,		//  M Input (Var)
 0x16, W(230),		//  G Logical Minimum (230)
 0x26, W(370),		//  G Logical Maximum (370)
 0x36, W(233),		//  G Physical Minimum (233 K = -40 °C)
 0x46, W(358),		//  G Physical Maximum (358 K = +85 °C)
 0x55, 0,		//  G Unit Exponent (0)
 0x67, D(0x00010001),	//  G Unit (Kelvin)
 0x0B, D(0x00840036),	//  L Usage (0x84/0x36, Temperatur)
 0x81, 0x02,		//  M Input (Var)
 0xB4,			//  G Pop

 0xA4,			//  G Push
 0x85, 5,		//  M Report ID (5)
 0x25, 10,		//  G Logical Maximum (10)
 0x75, 4,		//  G Report Size (4)
 0x95, 2,		//  G Report Count (2)
 0x09, 0x23,		//  L Usage (Empfangsbits-Indizes)
 0x25, -1,		//  G Logical Maximum (-1)
 0xB1, 0x82,		//  M Feature (Var,Volatile)
 0x75, 64,		//  G Report Size (64)
 0x95, 20,		//  G Report Count (20)
 0x09, 0x24,		//  L Usage (Empfangsbits)
 0xB1, 0x82,		//  M Feature (Var,Volatile)
 0x75, 16,		//  G Report Size (16)
 0x95, 50,		//  G Report Count (50)
 0x09, 0x25,		//  L Usage (Histogramm)
 0xB1, 0x82,		//  M Feature (Var,Volatile)
 0xB4,			//  G Pop

 0x85, 6,		//  M Report ID (6)
 0x09, 0x26,		//  L Usage (Current MJD = Modified Julian Date)
 0xB1, 0x82,		//  M Feature (Var,Volatile)
 0x75, 16,		//  G Report Size (16)
 0x26, W(479),		//  G Logical Maximum (479)
 0x09, 0x27,		//  L Usage (Current Index)
 0xB1, 0x82,		//  M Feature (Var,Volatile)
 0x75, 32,		//  G Report Size (32)
 0x25, -1,		//  G Logical Maximum (-1)
 0x96, W(480),		//  G Report Count (480 DWORDs)
 0x09, 0x28,		//  L Usage (Cache24 Entries)
 0xB1, 0x82,		//  M Feature (Var,Volatile)

 0xC0,
};			// M End Collection

#undef D
#undef W

static uchar* IoPtr;
static size_t IoLen;

/***************************
 * Wenn SETUP-Daten kommen *
 ***************************/

// Cache24-Daten anhängen bzw. darin umleiten
// Vom StatusReport flüchtige Bits wegnehmen
static bool HandleTail(void) {
 if (IoLen) return false;
 GPIOR0&=~0x80;
 if (IoPtr==(uchar*)&StatusReport+sizeof StatusReport) {
  StatusReport.someBits&=0x0F;
 }
 if (IoPtr!=(uchar*)&WetterReport+sizeof WetterReport) return false;
 IoPtr=(uchar*)Cache24;	// verlängern
 IoLen=480*4;
 GPIOR0|=0x80;
 return true;
}

uchar usbFunctionRead(uchar *data, uchar len) {
 uchar ret = 0;
 if (IoLen && len) {
  uchar rlen=len-IoLen;
  if (len > IoLen) len = (uchar)IoLen;
  if (GPIOR0&0x80) flash_read(data,IoPtr,len);
  else memcpy(data,IoPtr,len);
  IoPtr+=len;
  ret+=len;
  IoLen-=len;
  if (HandleTail()) ret+=usbFunctionRead(data+len,rlen);
  return ret;	// Anzahl erwarteter Bytes
 }
 return 0xFF;
}

uchar usbFunctionWrite(uchar *data, uchar len) {
 if (IoLen && len) {
  uchar rlen=len-IoLen;
  if (len > IoLen) len = IoLen;
  if (GPIOR0&0x80) {
   flash_write(IoPtr,data,len);
   if (IoLen==len) flash_flush();
  }else memcpy(IoPtr,data,len);
  IoPtr+=len;
  IoLen-=len;
  if (HandleTail()) return usbFunctionWrite(data+len,rlen);
  return !IoLen;	// 1 für Transmissions-Ende
 }
 return 0xFF;	// Stall, unerwartete Daten
}

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) {	// 6
   switch (rq->wValue.bytes[0]) {
    case 1: usbMsgPtr = (uchar*)&JoyReport; return sizeof JoyReport;	// 2
    case 2: Capture();
            TimeReport.ms=T0Capture-lastStart;
            usbMsgPtr = (uchar*)&TimeReport; return sizeof TimeReport;	// 10
    case 3: usbMsgPtr = (uchar*)&SetupReport; return sizeof SetupReport;// 6
    case 4: usbMsgPtr = (uchar*)&StatusReport; return sizeof StatusReport;// 6
    case 5: IoPtr = (uchar*)&HistoryReport; goto setlen;
    case 6: IoPtr = (uchar*)&WetterReport; IoLen=sizeof(WetterReport); return 0xFF;
   }
  }else if (rq->bRequest == USBRQ_HID_SET_REPORT) {
   switch (rq->wValue.bytes[0]) {
    case 3: IoPtr = (uchar*)&SetupReport; goto setlen;
    case 5: IoPtr = (uchar*)&HistoryReport; goto setlen;
    case 6: IoPtr = (uchar*)&WetterReport; IoLen=sizeof(WetterReport); return 0xFF;
   }
  }
 }
 return 0;
setlen:
 IoLen = rq->wLength.word;
 return 0xFF;
}

/*******************
 * Initialisierung *
 *******************/
 
static void hardwareInit(void) {
 PRR    = 0x0B;		// alles aus, außer Timer0
			// Der Timer1 schluckt viel mehr Strom als Timer0!
			// Der ADC wird nur gelegentlich aktiviert.
 DDRB   = 1<<ENA_BIT|1<<BAT_BIT;	// Ausgänge
 PORTB |= SIG_PU<<SIG_BIT;
 PORTB |= !ENA_INV<<ENA_BIT;
 PORTB |= !BAT_INV<<BAT_BIT;
 DIDR0 |= 1<<ANA_BIT;
 PCMSK  = 1<<SIG_BIT;	// Pegelwechselinterrupt (nur) an diesem Eingang
 GIMSK  = 0x60;		// INT0 sowie Pegelwechsel-Interrupt
 TCCR0B = 0x02;		// Achtel-Taktfrequenz (für USB und Zeitmessungen)
 TIMSK  = 0x02;		// Überlauf-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 lastValue;
#define SOLL 14	// usbSofDiff sollte 16500/8%256 = 14.5 sein
#define MABW 7	// Abweichung kleiner ±6 ist OK und wird ignoriert
 schar t = OCR0B-lastValue-SOLL;	// zentriere Ergebnis um die Null
 lastValue = OCR0B;
 GPIOR0&=~0x20;
 if (t<-MABW) OSCCAL++;	// schneller machen
 if (t> MABW) OSCCAL--;	// langsamer machen
}

/*****************
 * 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
   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();
 JoyReport.reportID=1;
 TimeReport.reportID=2;
 SetupReport.reportID=3;
 StatusReport.reportID=4;
 HistoryReport.reportID=5;
 WetterReport.reportID=6;
 usbInit();
 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();
  if (GPIOR0&0x20) tuneOscillator();
  usbPoll();
  dcf77Poll();
  if (usbInterruptIsReady()) {
   if (GPIOR0&1) {	// Pegelwechsel aufgetreten?
    usbSetInterrupt((uchar*)&JoyReport,sizeof JoyReport);
    GPIOR0&=~1;
   }else if (GPIOR0&2) {
    usbSetInterrupt((uchar*)&StatusReport,sizeof StatusReport);
    GPIOR0&=~2;
   }
  }
 }
}
Detected encoding: UTF-80