Source file: /~heha/basteln/PC/FunkUsb/dcf77franz9.zip/dcf77.h

#pragma once
/* Mikrocontroller: ATtiny85 bei 3..5 V
		╔════╤╤════╗
frei	PB5	╢ 1  └┘	 8 ╟ Ucc	5P
Quarz	PB3/XT1	╢ 2	 7 ╟ PB2/ADC1	Eingang für Antennenverstärker-Ausgang
Quarz	PB4/XT2	╢ 3	 6 ╟ PB1/DO	UART-TxD, später USB D-
00	GND	╢ 4	 5 ╟ PB0/PCINT0	LED,		 USB D+
		╚══════════╝
  
DCF77-Antenneneingang
	Pin7 = PB2 = ADC1
	Spannungs: Ueff: 1 mV .. 1,8 V (bei Ucc = 5 V)
	bzw. Uss: 2,5 mV .. 5 V
	Gleichspannung wird weggerechnet.
	Nachts um drei ist der beste Empfang: > 20 dB mehr Spannung!
   
Quarz: 10..20 MHz, optimal 15 MHz, muss 60, besser 20 ppm genau sein.
	Die Abweichung wird gemessen und ausgegeben, siehe „phasedreh“.
	Bei 15 MHz läuft der ADC mit < 1 MHz
	und Timer0 ohne Vorteiler für minimalen Phasen-Jitter

Peripherie:
Timer0:	Taktgeber für ADC und (neu!) serielle Schnittstelle
Timer1:	frei
USI:	Zur Bereitstellung hardware-genauer Taktflanken
ADC:	Nur ADC1: Geradeausempfänger mit Software-Quarzfilter

Während des Empfangs der beiden Datenbits sollten Störungen vermieden werden
	UART-Datenbits stören, daher in den Zehntelsekunden 2..9

Berechnungstafel: DCF77
f0 = 77500 Hz (T0 ≈ 12,90 µs)	in 4 ms sind das 310 Schwingungen
fA = 62000 Hz (TA ≈ 16,13 µs)	in 4 ms sind das 248 = 0xF8 Abtastwerte, pro „Eimer“ ÷4 = 62
MSF60:
f0 = 60000 Hz (T0 ≈ 16,67 µs)	in 4 ms sind das 240 Schwingungen
fA = 48000 Hz (TA ≈ 20,83 µs)	in 4 ms sind das 192 = 0xC0 Abtastwerte, pro „Eimer“ ÷4 = 48
Die „Eimer“ laufen clevererweise niemals über weil vom A/D-Wandler 10-Bit-Werte kommen.
*/
typedef unsigned char byte;
void idle();
#define elemof(x) (sizeof(x)/sizeof(*(x)))

union WordRec{
 unsigned u;
 int i;
 byte b[2];
 char c[2];
};

union LongRec{
 unsigned long d;
 long l;
 unsigned u[2];
 int i[2];
 byte b[4];
 char c[4];
 WordRec wr[2];
};

typedef byte Int24[3];

/* serial.cpp */
namespace UART{
// Die maximale Baudrate bei 16-MHz-Quarz dürfte bei 250k liegen, bei 8 MHz 125k
// Empfehlung: 115200
// Bei 4 MHz Quarzfrequenz funktioniert 115200 Baud nicht mehr,
// und bei 57600 Baud schlichen sich Bitfehler ein (ISR setzt das nächste Bit nun früher)
// Empfehlung: 57600, 38400
// Bei 2 MHz Quarzfrequenz muss die A/D-ISR ohnehin auf Tail-Chaining umgeschrieben werden.
// Das hat eine wesentlich niedrigere Baudrate zur Folge. Ungetestet.
constexpr double Baud = 57600;
void Init();
void Send(byte);
}
// String im Flash und mit MSB gesetzt (von-neumannisierte Adresse)
#define FP(x) *(decltype(&x))(size_t(&x)+0x8000)
#define F(x) ((const char*)((size_t)PSTR(x)+0x8000))
extern "C" void uprintf(const char*,...);
// p entspricht dem Typ "const char*&", d.h. p wird verändert, ist InOut-Argument.
// Das geht NUR mit Inline-Assembler! C++ ist zu doof um sowas zu erkennen.
#define read_byte_pp(p) ({byte R;asm("rcall read_byte_pp\n mov %0,r0":"=r"(R),"+z"(p));R;})
// Das klappt so, und so lässt sich auch EEPROM und ein evtl. Flashpuffer einbringen.

// Konstanten, die auch gebrochen sein dürfen
constexpr double fCarrier = 77500,	// DCF77-Sender
//constexpr double fCarrier = 60000,	// MSF60-Sender
/* berechnet */	 fAbtast = fCarrier*4/5;// Möglichst phasenstarre Abtastrate

// Berechnete Integer-Konstanten
enum{
// Timer0-Vorteiler =1 für geringen Phasenjitter, =8 wenn sich eine OCR0-Schrittweite > 255 ergibt
// Für DCF77 wäre dazu eine Quarzfrequenz unter 16 MHz anzustreben.
 TIM_Prescaler = F_CPU<15.9e6 ? 1 :  8, // (nächstmöglicher TIM0_CS-Wert ab "int((F_CPU/(fCarrier/1.25)+253)/254)")
// ADC-Vorteiler so dass zwischen 2 Abtastwerten mindestens 13,5 ADC-Takte zu liegen kommen.
// Und möglichst so, dass der ADC-Takt datenblattgerecht ≤ 1 MHz ist.
// Dieser Kompromiss erfordert Quarz zwischen 13,8 und 16 MHz. Oder zwischen 7 und 8 MHz.
// Daher ist mein Favorit 15 MHz, mit dem Hintergedanken der V-USB-Unterstützung.

// Die folgenden Konstanten steuern den Bitempfang.
// Der britische Zeitzeichensender MSF60 arbeitet etwas anders als DCF77 — und sendet keine Wetterdaten
 CarrierOnFrom = fCarrier==77500 ?  75 : 150, // ab welchem 4-ms-Wert ist Sender immer aktiv
};

// GPIOR0-Bits:
// 0: DPC in Idleprozedur bearbeiten
// 2: Basisindex für ISR_Buf: 0 oder 4
// Übrige Bits frei
// GPIOR1: Datenbits für UART
// GPIOR2: ADC-Sample innerhalb des 4-ms-Block, hier 0..0xF7

// Register müssen programmglobal für alle Quelldateien wegreserviert sein,
// auch wenn die Bezeichner nirgends mehr auftauchen
register unsigned isrsave1 asm("r2");
register unsigned isrsave2 asm("r4");
register int ISR_e asm("r6");	// Nachkomma von OCR0B
register long x1 asm("r8");
register unsigned long isrconst asm("r12");

// Kein RAM-Vielfraß mehr. Nur noch 24 Bytes. Zuzüglich 16 Bytes für ISR_Buf. Macht 40 Bytes.
extern struct DCF77{
 enum{
  EdgeDelay=20,	// Verzögerung der Flankenerkennung gegenüber Sendezeitpunkt, in 4 ms, hier üppige 80 ms, d.h. Millisekunden-Uhrzeit eher voreilend
  EdgeJitter=5,	// erlaubter ±-Bereich der fallenden Flanke für PLL-artige Uhr-Verstellung, hier ±20 ms
  Pickup0=EdgeDelay+8,	// Empfohlener Millisekunden-Zeitpunkt zum Bit-Lesen des Minutenbits, in 4 ms
  Pickup1=Pickup0+25,	// Dito für das Datenbit
 };
private:
 int Signal;	// Trägersignalpegel (fB=10Hz) am ADC-Eingang
			// - Umrechnung in db bezüglich VCC (Uss=VCC):
			//   db_VCC = (_Signal-5017)*0.023517 db
			//   - Konstanten
			//	 db_VCC = (_Signal-c)*m
			//	 c = log(1023*fCarrier/1.25/0.1s/4/2)/log(2)*256
			//	 m = 1/256*log(2)/log(10)*20
			// - Beispiel für _Signal=2400 und VCC=5V
			//   db_VCC = -61.5 db = (2400-5017)*0.023517 db
			//   UADCss  = 4200 µV = 10^(-61.5/20)*5 V
			//   UADCeff = 1487 µV = 10^(-61.5/20)/2/sqr(2)*5 V
// Echtes Rauschen kann mit Unterabtastung gar nicht gemessen werden!
// Der folgende Wert beschreibt das „Wackeln“ des vermeintlichen Trägers.
// Da die Filterung von FIR nach IIR umgestellt wurde, stimmen die Werte nicht mehr.
 int Noise;	// Rauschanteil beim Trägersignal
			// - Umrechnung in db
			//   db_Noise = _Noise*(0.023517/16) db
			// - Maß für die Bitfehlerhäufigkeit
			//  ≤0.3 db	 0.0000000000 %
			//   0.4 db	 0.0000000013 %
			//   0.5 db	 0.00001338 %
			//   0.6 db	 0.0003481 %
			//   0.7 db	 0.006739 %
			//   0.8 db	 0.05237 %
			//   0.9 db	 0.2176 %
			//   1.0 db	 0.6097 %
			//   1.1 db	 1.319 %
			//   1.2 db	 2.390 %
			//   1.3 db	 3.820 %
			//   1.4 db	 5.570 %
			//   1.5 db	 7.585 %
			//   1.6 db	 9.803 %
			//   1.7 db	12.16 %
			//   1.8 db	14.61 %
			//   1.9 db	17.11 %
			//   2.0 db	19.62 %
 char phase;	// Phase Senderträger im vergangenen 4-ms-Block in vier Quadranten (0..3 in den Bits 7:6)
 char phasedreh;	// Summe aller ±90°-Phasendrehungen in 1 s (-126..126)
// Mit "phasedreh" kann die Quarzfrequenz korrigiert werden:
// Faktor f = 0.25/fCarrier = 3.2258E-6 ≈ 3 ppm
// F_CPU += F_CPU * 3.2258E-6 * phasedreh
// Bei großen Abweichnungen (|phasedreh| > 30) ist's zu ungenau.
// Deshalb F_CPU um maximal 60 ppm ändern und Messung wiederholen.
// Zur besseren Vorstellung was ein ppm (parts per million) ist:
// - Gangabweichung einer (Quarz-)Uhr von 1 s/Tag = 11,6 ppm
// - Quarz „von der Stange“ und geschätzte Ballastkondensatoren an ATtiny85: 50 ppm
// - Derselbe Quarz nach Kalibrierung: 3 ppm (gelötet), 30 ppm (wackelkontaktig)
// - Auflösung des gebrochenen Frequenzteilers bei 16-MHz-Quarz als Q4.16: 1 ppm (20 Bit)
// - Dito bei 15 MHz, dann Q8.16 (24 Bit): 0,06 ppm
// - Bei 15 MHz verkürzter Frequenzteiler Q8.8 (16 Bit): 15 ppm (würde reichen)
// - Geforderte Bitzeit-Präzision bei V-USB: 2000 ppm = 2 ‰
public:
 byte hilo,lowi;	// „Zeitstempel“ für fallende Flanke und Lo-Länge in 4 ms
private:
// Leider hat der Autor ständig I und Q verwechselt!
// I ist der Realteil(!) wenn Q der Imaginärteil ist.
// Hier belasse ich die irreführende Benennung und Verwendung.
// 240704: Umstellung von FIR-Filter (frisst zu viel RAM) auf IIR-Filter
 Int24 QIAvg[2];		// Gleitender Mittelwert ×16 über T=16 (war: Summe aus 25)	(1. IIR-Tiefpass)
 int AmpMinMaxAvg[2];	// Minimum und Maximum des logarithmischen Betrags über T=256	(2. IIR-Tiefpass)
 byte bitcnt;	// zählt aufwärts bei 1-Bits bis max. 15 und abwärts bei 0-Bits (FIR-ähnlicher 3. Tiefpass)
 byte timeSafe;	// Bewertung der Sicherheit von timeCur, zur Steuerung der „PLL“
 byte timeCur;	// aktuelle Zeit innerhalb der Sekunde (0..249), durch PLL jitternd um ±4 ms,
		// Das bedeutet die Sekunden sind um ±4 ms underschiedlich lang. FIXME!
public:
 byte get_4ms() const {return timeCur;}
 bool get_bit() const {return bitcnt>=8;}
 char get_ppm() {char r=phasedreh; phasedreh=0; return r;}
 int getSignal() const {return Signal>>2;}
 int getNoise()  const {return Noise;}
 int getMinAmp() const {return AmpMinMaxAvg[0]>>2;}
 int getMaxAmp() const {return AmpMinMaxAvg[1]>>2;}
 static void Init();	// DCF77-Modul einschalten
 void QiBlockDpc();	// Von Idleprozedur aufzurufender DPC
}dcf77;
Detected encoding: UTF-80