/* 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;
}
}
}
}
Vorgefundene Kodierung: UTF-8 | 0
|