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

#include "dcf77.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <math.h>

volatile int ISR_Buf[2];

// Compiler berechnet Logarithmentafel zur Basis 2 im Bereich [1..2>
// Benötigt C++14 und erspart das Herumgewurstel mit externen Programmen
template<unsigned N> struct Logtab{
 byte a[N] {};	// lb(x) für x = [1..2>
 constexpr Logtab() {
  for (unsigned i=0; i<N; i++)
  a[i] = byte(256*log(1+double(i)/N)/log(2)+0.499);
 }
};

extern const Logtab<64> logtab;
const PROGMEM Logtab<64> logtab;

static constexpr char limit(int v) {
 if (v>+125) return +125;
 if (v<-125) return -125;
 return v;
}

#define makeAverages(X,Z) asm("rcall makeAverage$rcall makeAverage$sbiw ZL,6"::"x"(X),"z"(Z):"r20","r21","r22","r23","r24","r25")
#define phi(ptr) ({byte R;const Int24*Z=ptr;asm("rcall phi$mov %0,r24":"=r"(R):"z"(Z):"r24","r25");R;})

inline int mean(int a, int b) {	// Mittelwert aus a und b, egal ob vzb. oder vzl.
 asm(
"	add	%A0,%A1	\n"
"	adc	%B0,%B1	\n"
"	ror	%B0	\n"	// Überlaufbit wieder einschieben => alles richtig
"	ror	%A0	\n"
:"+r"(a):"r"(b));
 return a;
}

// „Asymmetrische“ Mittelwerte zur Medianfindung = Schaltschwelle für LED
static void handleMinMaxAvg(int v, int avg[2]) {
 int d = v-avg[0];
 avg[0] += (d<0) ? d>>4 : d>>8;	// Minimum schnell fallen, langsam steigen lassen
 d = v-avg[1];
 avg[1] += (d>=0)? d>>4 : d>>8;	// Maximum langsam fallen, schnell steigen lassen
}

inline byte add_sat(byte b,byte plus) {
 asm("	add	%0,%1	\n"
"	brcc	1f	\n"
"	sbc	%0,%0	\n"
"1:			\n"
:"+r"(b):"r"(plus));
 return b;
}
inline byte sub_sat(byte b,byte minus) {
 asm("	sub	%0,%1	\n"
"	brcc	1f	\n"
"	clr	%0	\n"
"1:			\n"
:"+r"(b):"r"(minus));
 return b;
}

static_assert(DCF77::EdgeJitter<DCF77::EdgeDelay,"Programmlogik erfordert EdgeJitter<EdgeDelay");
static_assert(DCF77::Pickup0>DCF77::EdgeDelay,"Programmlogik erfordert Pickup>EdgeDelay");

extern "C" int Cplx24AbsLog(Int24 avr[2]);

static char addphasedreh(char ph, char add) {
 if (add&0x40) {		// Nur bei ±64 Detektion einer Richtung möglich
  if (add>=0) {if (ph<126) ph+=3;
  }else if (ph>-126) ph-=3;
 }
 return ph;
}

//240706: Mit 930 Byte immer noch elend groß — in Assembler zu optimieren
//240716: Mit 616 Byte (C + ASM) geht's kaum kleiner — keine Laufzeitbibliothek mehr.
void DCF77::QiBlockDpc() { // 250× je Sekunde (4 ms)
// Slot-QI um neue ADC-Werte und den 10Hz-Filter aktualisieren
// Durch das IIR-Filter ist das Wackeln in den Zehntelsekunden 2..9 zu klein!
 makeAverages(ISR_Buf,QIAvg);

// Phasenänderungen von QI aufsummieren (für die ∆f-Messung)
 {char cPhi = phi(QIAvg),
       dPhi = cPhi-phase;		// nur 0, 0x40=64, 0x80=-128 und 0xC0=-64 möglich
  phase = cPhi;
  phasedreh = addphasedreh(phasedreh,dPhi);	// 0x80 = Phasenänderung 180° aber Richtung unbekannt
 }

// Amp: Amplitude des komplexen Mittelwertes berechnen
 int Amp = Cplx24AbsLog(QIAvg);

// Hier die neue Schwellenfindung, die nicht halluziniert.
// Das heißt, sie macht keine Annahmen über den ZEITPUNKT der Trägerabsenkung.
// Sie sucht selbständig die Maximal- und Minimalamplitude
// und benötigt kein #define für den Grad der Trägerabsenkung.
 int BigAmp=Amp<<2;			// <<2 läuft nicht über
 handleMinMaxAvg(BigAmp,AmpMinMaxAvg);
 int median = mean(AmpMinMaxAvg[0],AmpMinMaxAvg[1]);
 if (BigAmp>=median) {			// alle 4 ms ein neues Bit
  PORTB|=1;
  if (bitcnt<15 && ++bitcnt==15 && hilo && !lowi) {	// steigende Flanke absolviert
   lowi=timeCur-hilo;
/*   
   bool	q0 = 25+EdgeDelay-EdgeJitter<timeCur && timeCur<25+EdgeDelay+EdgeJitter,	// ± 32 ms um 100 ms für 0-Bit
	q1 = 50+EdgeDelay-EdgeJitter<timeCur && timeCur<50+EdgeDelay+EdgeJitter;	// dito    um 200 ms für 1-Bit
   quality = q0||q1 ? add_sat(quality,1) : sub_sat(quality,1);
*/
  }
 }else{
  PORTB&=~1;	// DEBUG
  if (bitcnt && !--bitcnt && !hilo) {		// fallende Flanke absolviert
   hilo=timeCur;
// Hingegen die Flankenerkennung versucht die „richtige Flanke“ zu finden
// und synchronisiert den 4-ms-Zähler mit der fallenden Amplitudenflanke
   if (timeSafe>=0x10) {			// PLL-Modus nach 2 Flanken
    if (EdgeDelay-EdgeJitter<timeCur && timeCur<EdgeDelay) {	// Max. 32 ms zu früh?
     ++timeCur;				// Okay, Zeit 4 ms nach hinten korrigieren
     timeSafe = add_sat(timeSafe,0x04);
    }else if (EdgeDelay<timeCur && timeCur<EdgeDelay+EdgeJitter) {	// Max. 32 ms zu spät?
     --timeCur;				// Okay, Zeit 4 ms nach vorn korrigieren
     timeSafe = add_sat(timeSafe,0x04);
    }else if (timeCur==EdgeDelay) timeSafe = add_sat(timeSafe,0x10);
    else timeSafe-=0x04;			// nach 30 falschen Flanken PLL-Modus verlassen
   }else{
    timeCur=EdgeDelay;			// Zeit hart setzen
    timeSafe+=0x08;
   }
  }
 }
 if (timeCur >= CarrierOnFrom) {
  int d = Amp-(Signal>>2);
  Signal += d;
  if (d<0) d=-d;
  Noise += d-(Noise>>4);
 }
 if (++timeCur==250) {
  timeCur = 0;		// Neue Sekunde beginnt
  hilo=lowi=0;
 }
}

void DCF77::Init() {
 enum{	// f2 hier 2114184 = 0x204288
  f2 = (unsigned long)((double)F_CPU/TIM_Prescaler/fAbtast*65536+0.5),	// eine Q8.16-Zahl
  nSamp = byte(fAbtast*4E-3), // hier: 248
 };
 static_assert(!(nSamp&3),"Muss durch 4 teilbar sein!");
// Weil der Addierwert in Registern steht kann dieser zur Laufzeit geändert werden!
// Keine Recompilierung bei Quarzfrequenzänderung erforderlich, wenn im EEPROM o.ä.
 isrconst = f2 | nSamp<<24;	// führt Konstanten für schnelle ISR
 GPIOR2 = 0;	// führt Samplezähler
// Timer 0 triggert A/D-Wandler. Jener benötigt 13,5 A/D-Takte ab Trigger.
 if (TIM_Prescaler==1) TCCR0B = 1;	// Quarz < 16 MHz
 if (TIM_Prescaler==8) TCCR0B = 2;	// Quarz ≥ 16 MHz
 TIMSK = 1<<OCIE0B;
// A/D-Wandler: Fest auf ADC1 = PB2
 ADMUX = 1; // ADC1 (PB2) und AREF = Ucc
 ADCSRB = 0b00000101; // Timer/Counter0 Compare Match B
 constexpr byte VT = log(F_CPU/1.725E6)/log(2)+1;	// 13,8 MHz ist die Grenze zwischen 3 (8) und 4 (16)
 ADCSRA = 0b10100000|VT;
}

DCF77 dcf77;
Detected encoding: UTF-80