#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;
Vorgefundene Kodierung: UTF-8 | 0
|