/* 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>
extern "C"{
#include "usbdrv.h"
}
#include "FunkUsb.h"
JoyReport joyReport;
TimeReport timeReport;
SetupReport setupReport;
StatusReport statusReport;
HistoryReport historyReport;
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() {
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() {
// wenn Batteriebetrieb:
// Timer1 starten mit Vorteiler 2048
flash_flush();
// T0NT
}
/**************************
* HID-Report-Beschreiber *
**************************/
#define B(x) char(x)
#define W(x) B(x),B((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)
B(0xA1), 1, // M Collection (Application)
B(0x85), 1, // M Report ID (1)
0x15, 0, // G Logical Minimum (0)
0x25, 1, // G Logical Maximum (1)
0x75, 1, // G Report Size (1)
B(0x95), 1, // G Report Count (1)
0x05, 9, // G Usage Page (Buttons)
0x09, 1, // L Usage (Button 1)
B(0x81), 0x02, // M Input (Var) // Trägerabsenkung
B(0x95), 7, // G Report Size (7)
B(0x81), 1, // M Input (Const) // Füllbits
B(0xC0), // M End Collection
0x06, W(0xFF00), // G Usage Page (Vendor Defined)
0x09, 0x01, // L Usage (Vendor Usage 1)
B(0xA1), 1, // M Collection (Application)
B(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)
B(0x95), 11, // G Report Count (9)
0x09, 0x21, // L Usage (DCF77-Uhrzeit: ms4,s,min,h,d,wd,mon,y,tz)
B(0xB1), 0x02, // M Feature (Var)
B(0x95), 1, // G Report Count (1)
B(0xA4), // G Push
B(0x85), 3, // M Report ID (3)
0x19, 0x31, // L Usage Minimum (Jahrhundert)
0x29, 0x35, // L Usage Maximum (Akkutyp)
B(0x95), 5, // G Report Count (5)
B(0xB1), 0x02, // M Feature (Var)
0x75, 16, // G Report Size (16)
B(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)
B(0xB1), 0x02, // M Feature (Var)
B(0xB4), // G Pop
B(0xA4), // G Push
B(0x85), 4, // M Report ID (4)
0x25, 1, // G Logical Maximum (1)
0x75, 1, // G Report Size (1)
B(0x95), 8, // G Report Count (8)
0x19, 0x40, // L Usage Minimum (Trägerabsenkung)
0x29, 0x47, // L Usage Maximum (Neuer Tag)
B(0x81), 0x02, // M Input (Var, Preferred State)
0x75, 16, // G Report Size (16)
B(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)
B(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)
B(0x81), 0x02, // M Input (Var)
B(0xB4), // G Pop
B(0xA4), // G Push
B(0x85), 5, // M Report ID (5)
0x25, 10, // G Logical Maximum (10)
0x75, 4, // G Report Size (4)
B(0x95), 2, // G Report Count (2)
0x09, 0x23, // L Usage (Empfangsbits-Indizes)
0x25, -1, // G Logical Maximum (-1)
B(0xB1), B(0x82), // M Feature (Var,Volatile)
0x75, 64, // G Report Size (64)
B(0x95), 20, // G Report Count (20)
0x09, 0x24, // L Usage (Empfangsbits)
B(0xB1), B(0x82), // M Feature (Var,Volatile)
0x75, 16, // G Report Size (16)
B(0x95), 50, // G Report Count (50)
0x09, 0x25, // L Usage (Histogramm)
B(0xB1), B(0x82), // M Feature (Var,Volatile)
B(0xB4), // G Pop
B(0x85), 6, // M Report ID (6)
0x09, 0x26, // L Usage (Current MJD = Modified Julian Date)
B(0xB1), B(0x82), // M Feature (Var,Volatile)
0x75, 16, // G Report Size (16)
0x26, W(479), // G Logical Maximum (479)
0x09, 0x27, // L Usage (Current Index)
B(0xB1), B(0x82), // M Feature (Var,Volatile)
0x75, 32, // G Report Size (32)
0x25, -1, // G Logical Maximum (-1)
B(0x96), W(480), // G Report Count (480 DWORDs)
0x09, 0x28, // L Usage (Cache24 Entries)
B(0xB1), B(0x82), // M Feature (Var,Volatile)
B(0xC0),
}; // M End Collection
#undef D
#undef W
#undef B
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() {
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 = reinterpret_cast<usbRequest_t*>(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 = timeReport; return sizeof timeReport; // 10
case 3: usbMsgPtr = setupReport; return sizeof setupReport;// 6
case 4: usbMsgPtr = statusReport; return sizeof statusReport;// 6
case 5: IoPtr = historyReport; goto setlen;
case 6: IoPtr = wetterReport; IoLen=sizeof wetterReport; return 0xFF;
}
}else if (rq->bRequest == USBRQ_HID_SET_REPORT) {
switch (rq->wValue.bytes[0]) {
case 3: IoPtr = setupReport; goto setlen;
case 5: IoPtr = historyReport; goto setlen;
case 6: IoPtr = wetterReport; IoLen=sizeof wetterReport; return 0xFF;
}
}
}
return 0;
setlen:
IoLen = rq->wLength.word;
return 0xFF;
}
/*******************
* Initialisierung *
*******************/
static void hardwareInit() {
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() {
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 __attribute__((OS_main)) main() {
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
// TODO: Nein! Der Vorlaufempfänger muss auch ohne USB-Aktivität laufen!
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(joyReport,sizeof joyReport);
GPIOR0&=~1;
}else if (GPIOR0&2) {
usbSetInterrupt(statusReport,sizeof statusReport);
GPIOR0&=~2;
}
}
}
}
| Detected encoding: UTF-8 | 0
|