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

/* Funkuhr-Empfänger, Umsetzung auf serielle Schnittstelle
 * Istby=240µA, Irun=6,3mA (mit heller LED (3mA), ohne Funkempfängermodul)
 * Textfile properties: TABSIZE = 8, CHARSET = Windows-1252
 080908	Erste Ausgabe
+130403	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.
 */

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

#include "usbdrv.h"

/* Standard-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  1	// 1 = PullUp erforderlich
#define ENA_BIT 4	// Empfänger-Freigabe
#define ENA_INV 1	// 1 = Low-aktiv

static uchar TxBuf;		// Sendepuffer (maximal 1 Byte)
static uchar TxBufLen;		// Füllstand (logischerweise 0 oder 1)
static uchar SerialState;	// auch "UART state bitmap", enthält:
// Bit	RS232	USB-Name	Inhalt (* = vom Funksignal beeinflusst)
// 0	DCD	bRxCarrier	*
// 1	DSR	bTxCarrier	*
// 2		bBreak		+	Stopbit(s) != 1 SOWIE längere Zeit LOW
// 3	RI	bRingSignal	*
// 4		bFraming	+	Stopbit(s) != 1
// 5		bParity		0
// 6		bOverrun	+	Empfangspuffer-Überlauf 
// Nirgendwo gibt's eine CTS-Entsprechung!
//Bei Änderung von SerialState wird automatisch eine entsprechende
//CDC-Notification abgesetzt (via Interrupt-Pipe).
//Deren Auswertung mit Funkuhr.exe funktioniert gut.

static uchar bitno;	// 0=keine Aktivität, 1=Startbit, 2-9=Datenbit, 10=Stopbit
static schar mscount;	// Zähler für Millisekunden (Abtastzeitpunkte)

ISR(PCINT0_vect) {	// Pegelwechsel-Interrupt (Funkuhr)
 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
  SerialState &= ~0x0B;
  if (!bitno) {
   mscount = 10;	// let re-check start bit after 10 ms (half bit slot)
   bitno = 1;		// start pollRxD() activity
  }
 }else{
#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
  SerialState |= 0x0B;
 }
}

// This routine is normally called every ms (ms==1),
// but seldom with more distance (ms>1).
// It checks the state of DCF77 signal (the so-called RxD pin)
// and forms a data byte inclusive BREAK/FRAMING ERROR condition
void pollRxD(uchar ms) {
 static uchar recv;
 if (!bitno) return;	// do nothing, no falling edge detected
 mscount -= ms;		// count milliseconds down
 if (mscount>=0) return;
 mscount += 20;		// Next time, check after 20 ms (50 Baud)
 switch (bitno) {
  case 1: {		// This is the start bit
   bitno++;		// prepare to receive data bit 0
   SerialState &= ~0x74;// remove flags
   if (PINB&0x08) {	// When the start bit disappears...
    bitno = 0;		// terminate RxD polling, await next H->L slope
   }
  }return;
  case 10: {		// This is the stop bit
   if (!(PINB&0x08)) {	// When stop bit is wrong...
    SerialState |= 0x14;	// set the BREAK flag
   }
   TxBuf = recv;
   TxBufLen = 1;	// make received character available
   bitno = 0;		// always terminate RxD polling
  }return;
 }
 recv >>= 1;
 if (PINB&0x08) recv |= 0x80;	// insert sampled bit
 bitno++;
}

/*********************
 * USB and CDC stuff *
 *********************/

enum {
 SET_LINE_CODING = 0x20,
 GET_LINE_CODING,
 SET_CONTROL_LINE_STATE,
 SEND_BREAK};

PROGMEM char usbDescriptorConfiguration[] = {   /* USB configuration descriptor */
 9,	/* sizeof(usbDescrConfig): length of descriptor in bytes */
 USBDESCR_CONFIG,	/* descriptor type */
 67,0,	/* total length of data returned (including inlined descriptors) */
 2,	/* number of interfaces in this configuration */
 1,	/* index of this configuration */
 0,	/* configuration name string index */
 USBATTR_BUSPOWER,	/* attributes */
 10/2,	/* max USB current in 2mA units */

/* interface descriptor */
 9,	/* sizeof(usbDescrInterface): length of descriptor in bytes */
 USBDESCR_INTERFACE, /* descriptor type */
 0,	/* index of this interface */
 0,	/* alternate setting for this interface */
 1,	/* endpoints excl 0: number of endpoint descriptors to follow */
 2,	/* control interface class: CDC */
 2,	/* control interface subclass: Abstract (Modem) */
 1,	/* control interface protocol: AT commands */
 0,	/* string index for interface */

/* CDC Class-Specific descriptor */
 5,	/* sizeof(usbDescrCDC_HeaderFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 0,	/* header functional descriptor */
 0x10,0x01,	// version number 1.10

 4,	/* sizeof(usbDescrCDC_AcmFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 2,	/* abstract control management functional descriptor */
 0x06,	/* SET_LINE_CODING, GET_LINE_CODING, SET_CONTROL_LINE_STATE, SERIAL_STATE */
// bit3=0 - Device doesn't support the notification Network_Connection.
// bit2=1 - Device supports the request Send_Break
// bit1=1 - Device supports the request combination of Set_Line_Coding,
//	    Set_Control_Line_State, Get_Line_Coding, and the
//	    notification Serial_State.
// bit0=0 - Device doesn't support the request combination of
//	    Set_Comm_Feature, Clear_Comm_Feature, and Get_Comm_Feature.

 5,	/* sizeof(usbDescrCDC_UnionFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 6,	/* union functional descriptor */
 0,	/* CDC_COMM_INTF_ID */
 1,	/* CDC_DATA_INTF_ID */

 5,	/* sizeof(usbDescrCDC_CallMgtFn): length of descriptor in bytes */
 0x24,	/* descriptor type */
 1,	/* call management functional descriptor */
 3,	/* allow management on data interface, handles call management by itself */
// bit1=1 - Device can send/receive call management information over a Data Class interface.
// bit0=1 - Device handles call management itself.
 1,	/* CDC_DATA_INTF_ID */

/* Endpoint Descriptor */
 7,	/* sizeof(usbDescrEndpoint) */
 USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
 0x83,	/* IN endpoint number 3 */
 0x03,	/* attrib: Interrupt endpoint */
 8,0,	/* maximum packet size */
 10,	/* polling interval in ms */

/* Second interface Descriptor */
 9,	/* sizeof(usbDescrInterface): length of descriptor in bytes */
 USBDESCR_INTERFACE,           /* descriptor type */
 1,	/* index of this interface */
 0,	/* alternate setting for this interface */
 2,	/* endpoints excl 0: number of endpoint descriptors to follow */
 0x0A,	/* Data Interface Class */
 0,	/* Data Interface Subclass */
 0,	/* Data Interface Class Protocol Codes */
 0,	/* string index for interface */

/* Endpoint Descriptor */
 7,           /* sizeof(usbDescrEndpoint) */
 USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
 0x01,	/* OUT endpoint number 1 */
 0x02,	/* attrib: Bulk endpoint */
 8,0,	/* maximum packet size 8->6 */
 0,

/* Endpoint Descriptor */
 7,           /* sizeof(usbDescrEndpoint) */
 USBDESCR_ENDPOINT,  /* descriptor type = endpoint */
 0x81,	/* IN endpoint number 1 */
 0x02,	/* attrib: Bulk endpoint */
 8,0,	/* maximum packet size */
 0,
};

static uchar requestType;
PROGMEM static uchar lineCoding[7]={
 50,0,0,0,	// 50 baud
 0,		// 1 stop bit
 0,		// no parity
 8};		// 8 data bits
static uchar lastLen;	// if 8, send an empty BULK packet


/***********************
 * SETUP data received *
 ***********************/
uchar usbFunctionSetup(uchar data[8]) {
 usbRequest_t *rq = (void*)data;
/* class request type (x01x xxxx) */
 if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
  switch (rq->bRequest) {
   case GET_LINE_CODING:
   case SET_LINE_CODING: {
    requestType = rq->bRequest;
   }return 0xff;
/*    GET_LINE_CODING -> usbFunctionRead()    */
/*    SET_LINE_CODING -> usbFunctionWrite()    */

   case SET_CONTROL_LINE_STATE:
// Bit1(wValue) = RTS (stets 0 unter Windows!)
// Bit0(wValue) = DTR
// other bits shall be 0 
   case SEND_BREAK: {
// wValue = 0: CLEAR_BREAK_STATE
// wValue = FFFFh: SET_BREAK_STATE (infinite)
// wValue = other: SET_BREAK_STATE (wValue milliseconds)
    if (rq->wValue.word) {
     PORTB &= ~0x10;
     DDRB |= 0x10;		// Steuerausgang LOW
// Report serial state (carrier detect). On several Unix platforms,
// tty devices can only be opened when carrier detect is set.
     SerialState = 3;
    }else{
     DDRB &= ~0x10;		// Steuerausgang hochohmig (mit Pullup)
     PORTB |= 0x10;
     SerialState = 0;
    }
   }break;
  }

/*  Prepare bulk-in endpoint to respond to early termination   */
  if ((rq->bmRequestType & USBRQ_DIR_MASK) == USBRQ_DIR_HOST_TO_DEVICE)
   lastLen=8;	// send an empty packet
 }
 return 0;
}

/***********************
 * IN transfer for EP0 *
 ***********************/
uchar usbFunctionRead(uchar *data, uchar len) {
 if (requestType == GET_LINE_CODING) {
  memcpy_P(data,lineCoding,7);
  return 7;
 }
 return 0;	/* error -> terminate transfer */
}


/************************
 * OUT transfer for EP0 *
 ************************/
uchar usbFunctionWrite(uchar *data, uchar len) {
// Baudrate usw. ist FEST!
 return 1;	/* accept everything until end */
}

/************************************
 * OUT transfer for EP1 (Bulk data) *
 ************************************/
void usbFunctionWriteOut(uchar *data, uchar len) {
 return;		// alle Ausgabebytes landen in der Mülltonne!
}

/******************
 * Initialization *
 ******************/
 
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) {
#define SOLL 14	// usbSofDiff should be 16500/8%256 = 14.5
#define MABW 7	// Deviation of less than ±6 is OK and silently ignored
 schar t = usbSofDiff-SOLL;	// center result around signed zero
 if (t<-MABW) OSCCAL++;		// make faster
 if (t> MABW) OSCCAL--;		// make slower
}

/*****************
 * Hauptprogramm *
 *****************/
int __attribute__((noreturn)) main(void) {
 uchar SofCmp = 0;
 uchar mcusr = MCUSR;
 MCUSR = 0;
 ACSR |= 0x80;		// disable analog comparator and save 70µA
 wdt_disable();		// watchdog survives RESET: disable it
 if (mcusr & (1 << WDRF)) {	// Wenn die Reset-Ursache der Watchdog war...
  if (USBIN & USBMASK) {	// no SE0 state?
   GIMSK = 0x40;
   MCUCR = 0x30;	// SLEEP_MODE_PWR_DOWN and level interrupt for INT0
#if ENA_INV
   PORTB |= 1<<ENA_BIT;	// Funkuhr-Freigabe: ausschalten! (Pullup)
#else
   PORTB &=~(1<<ENA_BIT);
#endif
   sei();		// must be enabled for wakeup
   sleep_cpu();		// stop osc. and let INT0 do the next wakeup
   cli();		// INT0 aufgetreten (kann nur USB-RESET == SE0 sein)
  }
 }
 MCUCR=0x20;		// SLEEP_MODE_STANDBY
 hardwareInit();
 wdt_enable(WDTO_15MS);
 usbInit();
 sei();

 for(;;){	// Endlos-Hauptschleife, wird höchstens vom Watchdog abgebrochen
  uchar t,d;
  if (USBIN&USBMASK) sleep_cpu();	// sleep, except at SE0, until SOF
  t=usbSofCount;	// atomic access to volatile variable
  d=t-SofCmp;		// time difference [ms], mostly 1
  if (d || !(USBIN&USBMASK)) wdt_reset();
  usbPoll();
  if (d) {		// After 1 ms (or maybe more)
   SofCmp=t;
   tuneOscillator();
   pollRxD(d);
  }
        /*    device -> host    */
  if (usbInterruptIsReady()) {
   uchar len = TxBufLen;
   if (len || lastLen&8 ) {
    usbSetInterrupt(&TxBuf,len);
    TxBufLen = 0;
/* let send an empty block after last data block to indicate transfer end */
    lastLen=len;
   }
  }
  
        /* We need to report rx and tx carrier after open attempt */
  if (usbInterruptIsReady3()) {
   static uchar serialStateNotification[10] = {
     0xa1, 0x20, 0, 0, 0, 0, 2, 0, 0, 0};
   static uchar sendTail;
   if (!sendTail && SerialState!=serialStateNotification[8]) {
    serialStateNotification[8] = SerialState;
    usbSetInterrupt3(serialStateNotification,8);
    sendTail = 1;
   }else if (sendTail) {
    usbSetInterrupt3(serialStateNotification+8,2);
//    serialStateNotification[8] &= ~4;	// BREAK löschen
    sendTail = 0;
   }
  }
 }
}
Detected encoding: UTF-80