/* 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 <avr/signature.h>
#include <avr/fuse.h>
#include <util/delay.h>
#include "usbdrv.h"
FUSES={0xE1,0xD5,0xFF};
/* 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};
const 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;
const 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__((OS_main)) 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-8 | 0
|