/******************************************
* DCF77-Funkuhr-Empfänger über serielles *
* oder paralleles Port *
* haftmann#software, August 2008 *
******************************************/
#include "Funkuhr.h"
#include <winioctl.h>
#define IOCTL_USBPRINT_GET_LPT_STATUS CTL_CODE(FILE_DEVICE_UNKNOWN,12,METHOD_BUFFERED,FILE_ANY_ACCESS)
/**** Zeittelegramm ****
0 Minutenstartbit (immer 0)
1 - 14 Wettervorhersage (verschlüsselt), Katastrophenwarnung (siehe mtDekoder.c)
15 Rufbit (bis Mitte 2003: Reserveantenne)
16 "1": Am Ende dieser Stunde wird MEZ/MESZ umgestellt
17 "0": MEZ, "1": MESZ
18 "0": MESZ, "1": MEZ
19 "1": Am Ende dieser Stunde wird eine Schaltsekunde eingefügt
20 Startbit (ist immer "1")
21 - 27 Minute, BCD, beginnend mit niederwertigstem Bit
28 Parität Minute
29 - 34 Stunde
35 Parität Stunde
36 - 41 Tag des Monats (1. = 1)
42 - 44 Wochentag (Mo. = 1, So. = 7)
45 - 49 Monat (Januar = 1)
50 - 57 Jahr (ohne Jahrhundert)
58 Parität Datum, kann entfallen bei Schaltsekunde==1 && Minute==0
(59) Füll-Bit (=0) NUR BEI Schaltsekunde==1 && Minute==0
Es wird stets die Zeit der _nächsten_ Minute gesendet.
Das vereinfachte nämlich früher den Aufbau von Digitaluhren.
Per Gesetz gibt's Schaltsekunden nur am 30. Juni 21:59:59 MESZ und am 31. Dezember 22:59:59 MEZ
***********************/
THid gHid;
static OVERLAPPED o; // für asynchrone Kommunikation - und zum Thread-Beenden
static HANDLE hThread;
static HANDLE hLibInOut;
static UINT TimerId,TimerPeriod;
static UINT JoyCapP1;
static BYTE (_stdcall*inportb)(WORD); // 2 Funktionszeiger
static void (_stdcall*outportb)(WORD,BYTE);
static DWORD StartTick;
#define BITTIME 20 // Millisekunden pro Schritt (Bit)
#define hCom gHid.hDev
const GUID GUID_DEVINTERFACE_USBPRINT = {
0x28d78fad,0x5a12,0x11D1,0xae,0x5b,0x00,0x00,0xf8,0x03,0xa8,0xc2};
// Macht aus <data> BCD->Binär-Wandlung,
// liefert 0xFF bei Pseudotetraden sowie bei Bereichsüberlauf
BYTE GetBCD(BYTE data, BYTE min, BYTE max) {
if ((data&0x0F)>=0x0A) return 0xFF; // Pseudotetrade!
if ((data&0xF0)>=0xA0) return 0xFF; // Pseudotetrade!
data-=(data>>4)*6; // BCD->Binär-Korrektur
if (data<min) return 0xFF;
if (data>max) return 0xFF;
return data;
}
// 1-Bits zählen, zur Paritätsprüfung
BYTE CountLsbBits(ULONG l, BYTE bits) {
BYTE r=0;
do{
if (l&1) r++;
l>>=1;
}while (--bits);
return r;
}
// Zeit-Telegramm einer Minute in SYSTEMTIME dekodieren
// Liefert <false> bei Fehler (dann kann »st« dennoch verändert sein!)
bool DecodeTime(ULONGLONG ll, MYSYSTEMTIME*st) {
__stosd((PDWORD)st,0,sizeof(*st)/4);
if ((UINT)ll&1) return false;
ll>>=16;
switch ((BYTE)ll&0x16) {
case 0x12: // MESZ und Startbit
case 0x14: break; // MEZ und Startbit
default: return false; // keine oder beide Bits gesetzt, kein Startbit
}
ll>>=5;
if (Config.Where==0 && Config.SerialLineIn==4) st->wMilliseconds=10*BITTIME;
// TODO (Feintuning): Geo-Position des Empfangsortes einrechnen (ergibt aber bloß 1 ms pro 300 km)
if (CountLsbBits((ULONG)ll,8)&1) return false; // falsche Parität Minute
if ((st->bMinute=GetBCD((BYTE)(ll&0x7F),0,59))==0xFF) return false;
ll>>=8;
if (CountLsbBits((ULONG)ll,7)&1) return false; // falsche Parität Stunde
if ((st->bHour=GetBCD((BYTE)(ll&0x3F),0,23))==0xFF) return false;
ll>>=7;
if (CountLsbBits((ULONG)ll,23)&1) return false; // falsche Parität Datum
if ((st->bDay=GetBCD((BYTE)(ll&0x3F),1,31))==0xFF) return false;
ll>>=6;
if ((st->bDayOfWeek=GetBCD((BYTE)(ll&0x07),1,7))==0xFF) return false; // Windows prüft das nicht
ll>>=3;
if ((st->bMonth=GetBCD((BYTE)(ll&0x1F),1,12))==0xFF) return false;
ll>>=5;
if ((st->bYear=GetBCD((BYTE)ll,0,99))==0xFF) return false;
st->st.wYear+=2000; // es gibt in absehbarer Zukunft kein 21xx
return true;
}
// History löschen
void ClearHisto(void) {
__stosd((DWORD*)Empfang.Histo,0,sizeof(Empfang.Histo)/4);
}
// Ermittelte Impulslänge in Histogramm eintragen,
// ggf. Invertierung umschalten
// liefert Index, bei dem die Impulslänge eingetragen wurde
static BYTE AddHisto(DWORD ms) {
DWORD sum;
ms/=10; // 10-ms-weise eintragen
if (ms>49) ms=49; // begrenzen
if (Empfang.Histo[ms]!=MAXWORD) Empfang.Histo[ms]++;
if (ms!=49) return (BYTE)ms; // langer Puls - nur Empfangsproblem?
if (Empfang.Histo[49]<10) return 49;
sum=0; do{ // übrige Pulse gegenrechnen
sum+=Empfang.Histo[--ms];
}while (ms);
if (sum>Empfang.Histo[49]) return 49; // nicht invertieren
Empfang.Invers=!Empfang.Invers; // invertieren
ClearHisto(); // Histogramm jetzt tünneff
return 0xFF; // "ungültig"
}
static BOOL CALLBACK EnumThreadWndProc(HWND Wnd, LPARAM lParam) {
TCHAR n[8];
GetClassName(Wnd,n,elemof(n));
if (!lstrcmpi(n,KARTECLASSNAME))
PostMessage(Wnd,WM_FUNKRECV,LOWORD(lParam),HIWORD(lParam));
return TRUE;
}
// Postet WM_FUNKRECV an aktive Eigenschaftsseite
// sowie an alle geöffneten Kartendarstellungen
void InfoPropSheet(WPARAM wParam, LPARAM lParam) {
if (PropWnd) {
HWND PageWnd=PropSheet_GetCurrentPageHwnd(PropWnd);
if (PageWnd) PostMessage(PageWnd,WM_FUNKRECV,wParam,lParam);
}
if (wParam&0x10) { // nur „neue Wetterdaten“ und „Tageswechsel“
EnumThreadWindows(GetCurrentThreadId(),EnumThreadWndProc,MAKELONG(wParam,lParam));
}
}
#define CurData Empfang.Data[Empfang.Index]
#define CurOkay Empfang.Okay[Empfang.Index]
#define IncMod
static DWORD PulsEndTick;
static bool summieren;
// Auswertung der Gesamtpulslänge (aus Teilstücken mit Lücken)
void OnTimerPulsAuswertung(void) {
DWORD Ticks=PulsEndTick-StartTick;
ULONGLONG CurMask=(ULONGLONG)1<<Empfang.Sek;
KillTimer(MainWnd,1);
if (Empfang.Signal) { // zu lang
CurData|=CurMask; // Fehler-Bit
}else{
if (Ticks<Config.ZuKurz) { // zu lang, zu kurz
CurData|=CurMask; // Fehler-Bit
}else if (Ticks<Config.Trenn){// 0-Bit (kurz)
CurOkay|=CurMask;
}else{ // 1-Bit (lang)
CurData|=CurMask;
CurOkay|=CurMask;
}
InfoPropSheet(1,AddHisto(Ticks));
}
summieren=false;
}
// Simulation einer 59. Sekunde
void OnTimerSek59(void) {
KillTimer(MainWnd,2);
Empfang.Sek++; // sollte 59 werden (60 bei Schaltsekunde?)
InfoPropSheet(2,0); // 59. Sekunde anzeigen
}
// Modifiziert Empfang.ZeitNeu derart, dass das Datum nicht verändert wird
static void PatchDatum(void) {
SYSTEMTIME st1,st2;
(Config.Checkmarks&32?GetSystemTime:GetLocalTime)(&st1);
FileTimeToSystemTime(&Empfang.ZeitNeu.ft,&st2);
*(ULONGLONG*)&st2=*(ULONGLONG*)&st1; // Tag kopieren
SystemTimeToFileTime(&st2,&Empfang.ZeitNeu.ft);
}
void OnSignalChange(bool Signal, DWORD Timestamp) {
DWORD Ticks;
Ticks=Timestamp-StartTick;
Empfang.Signal=Signal;
if (Signal) { // Impuls-Start
if (900<Ticks && Ticks<=1100) { // nichts
summieren=true;
SetTimer(MainWnd,1,Config.ZuLang<<1,NULL);
Empfang.Sek++;
if (Empfang.Luecke && Empfang.Sek==58) SetTimer(MainWnd,2,1000,NULL);
if (Empfang.Sek>=60) Empfang.Luecke=false;
}else if (1900<Ticks && Ticks<2100) {
summieren=true;
SetTimer(MainWnd,1,Config.ZuLang<<1,NULL);
Empfang.Sek++;
if (Empfang.Luecke) {
if (Empfang.Sek==60) { // Telegramm vollständig empfangen
if (DecodeTime(CurData,&Empfang.Dek)) {
U64 ft;
SystemTimeToFileTime(&Empfang.Dek.st,&ft.ft);
if (Config.Checkmarks&32) ft.ull-=HOUR2FILETIME(((DWORD)CurData>>17&1)+1);
if (!Empfang.LastOK || ft.ull==Empfang.ZeitNeu.ull+600000000) Empfang.LastOK++;
Empfang.ZeitNeu=ft;
if (Empfang.LastOK==2) { // 2 aufeinanderfolgende Telegramme
if (!(Config.Checkmarks&8)) PatchDatum();
if (Empfang.Ein&1) ComputerUhrStellen(); // Nicht bei Ein==2
Empfang.LastOK=1;
}
SetTrayTip();
}else{
Empfang.LastOK=0; // keine Zeiger zeigen!
SetTrayTip();
}
}else{
Empfang.Luecke=false; // neue Lücke suchen
}
}
Empfang.Index++;
if (Empfang.Index==MINU) Empfang.Index=0;
if (Empfang.Anzahl<MINU) Empfang.Anzahl++;
if (!Empfang.Anzahl) Empfang.Anzahl++; // mindestens 1
CurData=CurOkay=0;
Empfang.Sek=0;
Empfang.Luecke=true; // Lücke gefunden!
}else if (summieren && Timestamp-PulsEndTick<100) {
if (AnimYes()) AnimateTrayIcon();
return; // ignorieren
}else{
Empfang.Luecke=false;// Lücke neu suchen!
}
StartTick=Timestamp;
InfoPropSheet(0,0);
if (AnimYes()) {
MakeIcons();
AnimateTrayIcon();
}
}else{ // Impuls-Ende
if (summieren) {
PulsEndTick=Timestamp;
}else{
/*
BYTE hi;
ULONGLONG CurMask=(ULONGLONG)1<<Empfang.Sek;
if (Ticks<Config.ZuKurz) { // zu kurz
CurData|=CurMask; // Fehler-Bit
}else if (Ticks<Config.Trenn){ // 0-Bit (kurz)
// ClearCommBreak(hCom);
CurOkay|=CurMask;
}else if (Ticks<Config.ZuLang*2U) { // 1-Bit (lang)
CurOkay|=CurMask;
CurData|=CurMask;
}else{ // zu lang
CurData|=CurMask;
}
*/
InfoPropSheet(1,AddHisto(Ticks));
}
if (Decrypt && Empfang.Sek==15 && Empfang.Dek.bMinute%3==2) {
BYTE cipher[10];
long saveindex; // LOWORD = Index (0..479), HIWORD = Monat/Tag
if (BuildCipher(0,cipher,&saveindex)) {
long result=Decrypt(cipher);
DbgPrintf(("%s(%d) : Decrypt()=%X @ %d, MJD=%d\n",__FILE__,__LINE__,result,saveindex%480,saveindex/480));
if (result>=0) {
SetCache24Entry(saveindex,result);
SaveConfig(CACHE24);
InfoPropSheet(16,saveindex%480);
}
}
}
if (AnimYes()) AnimateTrayIcon();
}
}
/*************************************
*** Priorisierter Empfangs-Thread ***
*************************************/
/*** A: Serieller Empfangs-Thread ***/
static void AsyncSignalChange(bool Signal,DWORD TimeMark) {
Signal^=Empfang.Invers;
if (Config.Piep && AnimYes() && !Empfang.DauerPiep && Config.Where!=2) {
(Signal?StartBeep:StopBeep)();
}
PostMessage(MainWnd,WM_FUNKRECV,Signal,TimeMark);
} // hier: wParam = 0 oder 1, lParam = Millisekunden-Zähler
// ordnet SetXXX-Code der Ausgabeleitung zu
static const BYTE CodeFromLineOut[3]={
SETDTR,
SETRTS,
SETBREAK};
// ordnet Ereigniskode der Eingabeleitung zu
static const WORD EventFromLineIn[5]={
EV_CTS,
EV_DSR,
EV_RING,
EV_RLSD,
EV_BREAK|EV_RXCHAR};
// ordnet Statuskode der Eingabeleitung zu
static const BYTE StateFromLineIn[5]={
MS_CTS_ON,
MS_DSR_ON,
MS_RING_ON,
MS_RLSD_ON,
0}; // BREAK-Erkennung ist Sonderfall
// Alles unterdokumentierte von OVERLAPPED nullsetzen
void zeroOverlapped(OVERLAPPED*o) {
__stosd((DWORD*)o,0,(sizeof(OVERLAPPED)-sizeof(HANDLE))/4);
}
// Signalerkennung auf serieller Leitung
static DWORD CALLBACK UhrThread(LPVOID p) {
DWORD SerialEvent,SerialState,SerialError,deb;
DWORD WantEvent=EventFromLineIn[Config.SerialLineIn];
DWORD WantState=StateFromLineIn[Config.SerialLineIn];
DWORD tick;
if (!GetCommModemStatus(hCom,&SerialState)) { // Ohne das geht's nicht! (W2k)
PostMessage(MainWnd,WM_TryReOpen,0,0);
return 0;
}
SerialError=(DWORD)-1;
ClearCommError(hCom,&SerialError,NULL);
for(;;){
ResetEvent(o.hEvent);
if (Config.SerialLineIn==4) PurgeComm(hCom,PURGE_RXABORT|PURGE_RXCLEAR);
SerialEvent=WantEvent;
if (WaitCommEvent(hCom,&SerialEvent,&o)) ;
else /*if ((deb=GetLastError())==ERROR_IO_PENDING)*/ {
switch (deb=WaitForSingleObject(o.hEvent,Empfang.IntrOK?2000:10)) {
case 0: {
Empfang.IntrOK=true;
}break;
case WAIT_TIMEOUT: { // Ob sich hinter dem Rücken dennoch DSR änderte?
DWORD dw;
GetCommModemStatus(hCom,&dw);
if ((dw^SerialState)&WantState) { // Änderung der Signalleitung?
Empfang.IntrOK=false;
SerialEvent=WantEvent;
}else continue;
}break;
// default: DebugPrintf(T("WaitForSingleObject: %u\n"),deb);
}
/* }else{
DebugPrintf(T("WaitCommEvent: %u\n"),deb);
continue;*/
}
if (!SerialEvent) break; // abgebrochen durch Haupt-Thread
tick=GetTickCount();
if (Config.SerialLineIn==4) { // RxD zum Empfang (Trägerabsenkung = +12V)
DWORD t=0;
if (SerialEvent&EV_RXCHAR) { // "Zeichen" abholen und auswerten
BYTE chr;
DWORD nb;
zeroOverlapped(&o);
if (ReadFile(hCom,&chr,1,&nb,&o)) { // sollte nicht versagen
_BitScanForward(&t,chr|0xFFFFFF00u);
t++;
// t=bsf()+1;
}else{
// DebugPrintf("ReadFile: %u\n",GetLastError());
SerialError=(DWORD)-1;
ClearCommError(hCom,&SerialError,NULL);
Sleep(10); // Bug mit USBCDC abmildern (ohne Signal 100% Rechenlast)
}
}
if (SerialEvent&EV_BREAK) t=10; // langer Impuls (>190 ms)
if (t) {
t*=BITTIME; // in Millisekunden
AsyncSignalChange(true,tick);
Sleep(t);
AsyncSignalChange(false,tick+t); // tatsächliche Pulslänge mitteilen
}
}else if (SerialEvent&WantEvent) {
DWORD dw=SerialState; // alter Zustand
GetCommModemStatus(hCom,&SerialState); // Änderung?
if ((dw^SerialState)&WantState) {
bool Signal=(dw&WantState)!=0;
AsyncSignalChange(Signal,tick);
}else{
SerialError=(DWORD)-1;
ClearCommError(hCom,&SerialEvent,NULL);
Sleep(10); // Bug mit USBCDC abmildern (ohne Signal 100% Rechenlast)
}
}
}
return 0;
}
/*** B: Parallelport-Empfangs-Thread ***/
static WORD InAddr,OutAddr;
static BYTE InMask,OutMask,OutSave,InLast;
static bool InInv,OutInv;
// MMSYSTEM-Timer sind ebenfalls ein eigener Thread; wird im 10-ms-Takt gerufen
// Keine sinnvolle Möglichkeit für ein Resume-Problem
static void CALLBACK TimerProc(UINT TimerId, UINT uMsg,
DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) {
BYTE b=inportb(InAddr);
if ((b^InLast)&InMask) {
bool Signal=(b&InMask)!=0;
Signal^=InInv;
AsyncSignalChange(Signal,timeGetTime());
}
InLast=b;
}
/*** C: Soundkarten-Thread ***/
typedef struct{
double cos,sin;
}sincos_t;
typedef struct{
short ch,sh,cf,sf; // Werte in der Reihenfolge: Kosinus/Sinus aufsteigendes cos², absteigendes cos²-Fenster
}sintab_entry_t; // im Wertebereich ±16K
typedef struct{
double UsedAngle; // Benutzter Gesamtwinkel für Tabelle, im Bogenmaß
sincos_t UsedSC; // davon Sinus und Kosinus für I/Q-Drehung
sintab_entry_t e[];
}sintab_t;
struct{
HWAVEIN hWaveIn;
DWORD SamplesPerSec;
double f,w; // Auf Abtastrate umgerechnete Winkelgeschwindigkeit sowie Winkel des DDS-Generators
__int64 hi,hq; // „halbe“ Demodulationsergebnisse
WAVEHDR wh[10]; // Puffergröße immer 2^n für FFT zur Frequenzsuche
//#ifdef _DEBUG
// LARGE_INTEGER qpf;
//#endif
sintab_t *sintab;
}demod;
TDemodIQ DemodIQ;
#ifdef _M_IX86
__forceinline void fsincos(sincos_t*sc, double arg) {
_asm fld arg
_asm mov eax,sc
_asm fsincos
_asm fstp qword ptr[eax+0]
_asm fstp qword ptr[eax+8]
}
__forceinline __int64 imul64(int a, int b) {
_asm mov eax,a
_asm imul b
}
#else
extern void fsincos(sincos_t*,double);
#define imul64(a,b) ((__int64)(a)*(b))
#endif
#define LN32K 10.397207708399179641258481821873 // Bereich
// Config.iDemodTrig -> DemodIQ.at
// AM-Demodulator-Schaltschwelle von Hand einstellen
// iTrig == 0 -> at == 1
// iTrig == 63 -> at == 32768
void SetTrig() {
DemodIQ.at=lrint(exp((Config.iDemodTrig>>2)*LN32K/63))<<IQSHIFT;
}
// result = ln(t)*max/LN32K
int Logarithmize(int t, int max) {
if (t<0) t=0;
if (t) t=lrint(log(t)*max/LN32K);
if (t>max) t=max; // sollte nie vorkommen
return t;
}
// Hypotenuse berechnen, Integer-Version
int hypoti(int x, int y) {
return lrint(sqrt(imul64(x,x)+imul64(y,y)));
}
// Faule Hypotenusenabschätzung
int hypot_lazy(int x, int y) {
x=abs(x);
y=abs(y);
if (x>y) y>>=1; else x>>=1; // den kleineren Wert halbieren
return x+y; // Summe liefern, fertig
}
// Config.iDemodTrig <- DemodIQ.at
void UpdateTrig() {
int lnat=Logarithmize(DemodIQ.at>>IQSHIFT,63);
if (lnat!=Config.iDemodTrig>>2)
DbgPrintf(("UpdateTrig(%d->%d) AT<<%d=%d\n",Config.iDemodTrig>>2,lnat,IQSHIFT,DemodIQ.at));
Config.iDemodTrig=Config.iDemodTrig&3|lnat<<2;
}
__forceinline void Discriminate() {
int a=DemodIQ.a;
int at=DemodIQ.at;
static int LastDiff;
int Diff;
if (!DemodIQ.NoAGC) {
if (a>at) at=(int)((imul64(at,1023)+a)>>10); // 1/1024 drauf (wie ist dann Tau?)
else at=(int)((imul64(at,255)+a)>>8); // 1/256 weg
DemodIQ.at=at; // (asymmetrisch wegen kürzerer Trägerabsenkung; evtl. Median verwenden?)
}
Diff=a-at;
if ((LastDiff^Diff)<0) { // unterschiedliches Vorzeichen = Pegelwechsel
// hier: nicht piepsen, sonst hängt sich Windows auf!
int dt=MulDiv(LastDiff,1<<DemodIQ.FFT_LenShift,LastDiff-Diff); // Geradeninterpolation
dt+=DemodIQ.nBlocks<<DemodIQ.FFT_LenShift;
if (dt<0) dt=DemodIQ.nBlocks=0; // Überlaufsituation nur gelegentlich (TODO: Überläufe anders lösen!)
// Überlaufperiode bei 44100 Sa/s: rund 12 Stunden
AsyncSignalChange(Diff>>31&1,MulDiv(dt,1000,demod.SamplesPerSec));
LastDiff=Diff;
}
if (DemodIQ.hShowDemod) PostMessage(DemodIQ.hShowDemod,WM_RECV_IQ,DemodIQ.i,DemodIQ.q);
}
static void Demodulate(const short*data) {
int i,j,q,k; // auf 32 Bit (15+IQSHIFT=27 Bit) verkürzte Ergebnisse
__int64 hi,hq,fi,fq,n;
sincos_t sc;
//#ifdef _DEBUG
// LARGE_INTEGER tic,toc;
// QueryPerformanceCounter(&tic);
//#endif
sintab_t *T;
DemodIQ.lock|=2;
T=demod.sintab;
fi=demod.hi; // vorhergehende halbe Demodulationsergebnisse laden
fq=demod.hq;
n=hi=hq=0;
k=1<<DemodIQ.FFT_LenShift;
for(i=0; i<k; i++) { // Die innere Schleife kommt ohne Gleitkomma aus
sintab_entry_t *e=T->e+i;
int d=data[i];
n+=d*d;
hi+=d*e->ch; // parallelisierbar mit SSE:
hq+=d*e->sh; // f=*e;
fi+=d*e->cf; // d=__m64i_mm_shuffle_pi16(data[i],0)
fq+=d*e->sf; // __m128i_mm_add_epi32(a,__m128i_mm_unpackhi_epi32(__m64i_mm_mulhi_pi16(d,f),__m64i_mm_mullo_pi16(d,f)))
}
demod.hi=llrint( hi*T->UsedSC.cos+hq*T->UsedSC.sin);
demod.hq=llrint(-hi*T->UsedSC.sin+hq*T->UsedSC.cos); // „zurückdrehen“
demod.w=fmod(demod.w+T->UsedAngle,2*PI); // nächstes DDS-Winkelargument normalisieren
k=DemodIQ.FFT_LenShift;
i=(int)(fi>>k);
q=(int)(fq>>k);
fsincos(&sc,demod.w);
j=lrint(i*sc.cos-q*sc.sin);
q=lrint(i*sc.sin+q*sc.cos); // nachträgliche Korrektur (für feste Sinustabelle)
DemodIQ.i=j; // -32768..32768<<IQSHIFT (theoretisch)
DemodIQ.q=q;
DemodIQ.a=hypoti(j,q); // Amplitude (0..32768<<IQSHIFT)
DemodIQ.n=lrint(sqrt(n>>k)); // Rauschen (0..32768)
DemodIQ.lock&=~2;
//#ifdef _DEBUG
// QueryPerformanceCounter(&toc);
// DbgPrintf(("Zeit: %d ms\n",lrint((double)(toc.QuadPart-tic.QuadPart)*1000/demod.qpf.QuadPart)));
//#endif
// DbgPrintf(("Demodulate: I=%d, Q=%d, A=%d, N=%d, W=%d°\n",hi,hq,DemodIQ.a,DemodIQ.n,lrint(demod.w*180/PI)));
Discriminate();
}
static void CALLBACK waveInProc(HWAVEIN hwi, UINT msg, void*dwUser, WAVEHDR*dw1, void*dw2) {
switch (msg) {
case WIM_DATA: {
if (dw1->dwBytesRecorded==dw1->dwBufferLength) {
Demodulate((short*)dw1->lpData);
if (!(DemodIQ.lock&1) && DemodIQ.FFT_Data) {
// TODO: FFT
// TODO: Resume-Behandlung
}
if (DemodIQ.DoFFT) DemodIQ.DoFFT--;
DemodIQ.nBlocks++;
waveInAddBuffer(hwi,dw1,sizeof(WAVEHDR));
}
}
}
}
/*** D: Joystick-Thread ***/
static void CALLBACK JoyTimerProc(UINT TimerId, UINT uMsg,
DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) {
static bool LastState;
bool CurState;
DWORD TimeAxis = Config.iJoyButton>>5; // 0 = keine, sonst 1..7
JOYINFOEX ji;
InitStruct(&ji,sizeof(ji));
ji.dwFlags=(TimeAxis?1<<TimeAxis:0)|JOY_RETURNBUTTONS;
if (joyGetPosEx(Config.iJoystick,&ji)) { // wine BUG: joyGetPosEx liefert konstante Position
PostMessage(MainWnd,WM_TryReOpen,0,0); // Linux BUG: /dev/input/js0 liefert auch bei …
return; // … abgeschalteter Kalibrierung falsche Zeitstempel
}
CurState=ji.dwButtons>>(Config.iJoyButton&0x1F)&1;// aus bis zu 32 Knöpfen einer (x86 bräuchte das & nicht:-)
if (CurState!=LastState) {
switch (TimeAxis) {
case 0: TimeAxis=timeGetTime(); break;
case 7: TimeAxis+=2; nobreak; // dwButtons und dwButtonNumber überspringen, zu dwPOV gehen
default: {
static DWORD LastTime;
TimeAxis=(&ji.dwXpos)[TimeAxis-1]; // ANNAHME: liefert 16-Bit-Wert vom Mikrocontroller
TimeAxis=MAKELONG(LOWORD(TimeAxis),HIWORD(LastTime)); // Letztes High-Word einsetzen
if ((long)(TimeAxis-LastTime)<0) TimeAxis+=0x10000; // bei Umlauf High-Word erhöhen
LastTime=TimeAxis;
}
}
AsyncSignalChange(CurState,TimeAxis);
LastState=CurState;
}
}
/*** E: USB-Paralleldrucker-Konverter-Thread ***/
static void CALLBACK UsbPrnTimerProc(UINT TimerId, UINT uMsg,
DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) {
BYTE b;
DWORD dw,e;
static BYTE LastState;
if (!DeviceIoControl(hCom,IOCTL_USBPRINT_GET_LPT_STATUS,NULL,0,&b,1,&dw,&o)
&& ((e=GetLastError())!=ERROR_IO_PENDING || WaitForSingleObject(o.hEvent,10))) {
CancelIo(hCom);
if (e!=ERROR_IO_PENDING) PostMessage(MainWnd,WM_TryReOpen,0,0);
}else{
b>>=Config.iUsbInput-010; // oktal!
b&=1;
if (LastState!=b) AsyncSignalChange(LastState=b,timeGetTime());
}
}
/*** F: HID-Vorlaufempfänger-Thread ***/
int GetMaxReport(const HIDP_CAPS*caps) {
int ret=caps->InputReportByteLength;
if (ret<caps->OutputReportByteLength) ret=caps->OutputReportByteLength;
if (ret<caps->FeatureReportByteLength) ret=caps->FeatureReportByteLength;
return ret;
}
// ersetzt HidD_GetInputReport(), mit TimeOut bei FILE_FLAG_OVERLAPPED
bool GetInputReport(HANDLE hDev, void*buf, int len, OVERLAPPED*o) {
DWORD br;
zeroOverlapped(o);
return ReadFile(hDev,buf,len,&br,o)
|| GetLastError()==ERROR_IO_PENDING
&& !WaitForSingleObject(o->hEvent,100)
&& GetOverlappedResult(hDev,o,&br,FALSE)
&& br;
}
static DWORD CALLBACK HidThread(LPVOID p) {
PUCHAR report; // HID-Report-Puffer
int len=gHid.caps.InputReportByteLength;
report=LocalAlloc(LPTR,len<<1);
if (report) {
for (;;) {
HidP_InitializeReportForID(HidP_Input,4,gHid.pd,report,len);
if (!GetInputReport(hCom,report,len,&o)) break;
if (*(DWORD*)(report+len+2)!=*(DWORD*)(report+2)) {
*(DWORD*)(report+len+2)=*(DWORD*)(report+2);
if (PropWnd) {
HWND w=PropSheet_IndexToHwnd(PropWnd,0);
if (w==PropSheet_GetCurrentPageHwnd(PropWnd)) {
//SendMessage(w,WM_FUNKRECV,14,(LPARAM)report);
FillHidRep4(w,&gHid,report);
}
}
}
}
CancelIo(hCom);
LocalFree(report);
}
return 5;
}
/******************************
* Ab hier wieder Main-Thread *
******************************/
static void ClearSerialLines(void) {
EscapeCommFunction(hCom,CLRDTR);
EscapeCommFunction(hCom,CLRRTS);
EscapeCommFunction(hCom,CLRBREAK);
}
// ermittelt Invertierung einer Druckerport-Leitung
static bool GetInvFromLine(BYTE line) {
switch (line) {
case 017: // BSY (oktal!)
case 020: // STB
case 021: // AF
case 023: return true; // SEL
}
return false;
}
// Öffnet GUID-basiertes Device-Interface mit FILE_FLAG_OVERLAPPED
// Für UsbPrn und HID-Vorlaufempfänger verwendet
static HANDLE OpenDevInterface(const GUID *guid, int idx, DWORD access, DWORD share) {
HANDLE ret=0;
HDEVINFO devs=SetupDiGetClassDevs(guid,0,0,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
if (devs!=INVALID_HANDLE_VALUE) {
SP_DEVICE_INTERFACE_DATA devinterface;
devinterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiEnumDeviceInterfaces(devs,NULL,guid,idx,&devinterface)) {
struct{
SP_DEVICE_INTERFACE_DETAIL_DATA id;
TCHAR space[MAX_PATH];
}id;
id.id.cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (SetupDiGetDeviceInterfaceDetail(devs,&devinterface,&id.id,sizeof(id),NULL,NULL)) {
ret=CreateFile(id.id.DevicePath,access,share,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
if (ret==INVALID_HANDLE_VALUE) ret=0;
}
}
SetupDiDestroyDeviceInfoList(devs);
}
return ret;
}
void AllocFFTSpace() {
if (DemodIQ.DoFFT) {
if (!DemodIQ.FFT_Data) {
DemodIQ.FFT_Data=LocalAlloc(LPTR,(sizeof(fft_t)*3)<<DemodIQ.FFT_LenShift);
// Realteile, Imaginärteile (verschachtelt), Hanningfenster (dahinter)
if (DemodIQ.FFT_Data) {
fft_t *p=DemodIQ.FFT_Data+(2<<DemodIQ.FFT_LenShift);
int k=1<<DemodIQ.FFT_LenShift;
int i;
for (i=0; i<k; i++,p++) { // das optimiert der Compiler schon weg
*p=lrint((1-cos(i*2*PI/k))*(1<<IQSHIFT));
} // Hanningfenster-Faktoren berechnen (im Bereich 0..2<<IQSHIFT, also 8K)
}
}
}else{
if (DemodIQ.FFT_Data) DemodIQ.FFT_Data=LocalFree(DemodIQ.FFT_Data);
}
}
static sintab_t* GenSintab() {
int len=1<<DemodIQ.FFT_LenShift;
sintab_t*r=LocalAlloc(LMEM_FIXED,sizeof(sintab_t)+len*sizeof(sintab_entry_t));
if (r) {
int i;
double w=0;
sincos_t dds;
for(i=0; i<len; i++) {
sintab_entry_t *e=r->e+i;
double wa=(i+0.5)*PI/len; // Fensterungs-Argument _
double wf=0.5*(cos(wa)+1); // Fenster: zweite Hälfte _ ( \_)
double wh=1-wf; // Fenster: erste Hälfte (_/ )
fsincos(&dds,w); // Werte des Quadratur-DDS-Generators
e->ch=(short)lrint(dds.cos*wh*(1<<IQSHIFT));
e->sh=(short)lrint(dds.sin*wh*(1<<IQSHIFT));
e->cf=(short)lrint(dds.cos*wf*(1<<IQSHIFT));
e->sf=(short)lrint(dds.sin*wf*(1<<IQSHIFT));
w+=demod.f; // nächstes DDS-Winkelargument
}
r->UsedAngle=w;
fsincos(&r->UsedSC,w);
i=lrint(w*180/PI);
DbgPrintf(("GenSinTab Perioden=%d, Restwinkel=%d°\n",i/360,i%360));
}
return r;
}
// Sinustabelle neu berechnen
void SetFiltFreq(int f) {
if (DemodIQ.f!=f) {
int even=(f+100)/200;
int fein=f-even*200;
Config.iFiltFreq=(WORD)even;
Config.iAFC=(char)fein;
}
if ((DemodIQ.f!=f || !demod.f) && demod.SamplesPerSec) {
demod.f=f*0.02*PI/demod.SamplesPerSec; // Kreisfrequenz
if (demod.sintab) {
void*old=demod.sintab;
demod.sintab=GenSintab(); // in atomarer Schreiboperation Zeiger ersetzen
while (DemodIQ.lock&2) Sleep(0);
LocalFree(old);
}
}
DemodIQ.f=f;
}
// Liefert gerundete Filterfrequenz in ganzen (nicht nur geraden) Hertz
// <*frac> ist der Initialisierungswert für die Feinverstellung im Bereich -50..50
int FiltFreqHz(int*frac) {
int sum=Config.iFiltFreq*200+Config.iAFC;
int ret=(sum+50)/100; // neigt zum Aufrunden
int fra=sum-ret*100; // verbleibende Differenz
if (fra==-50 && ret&1) {fra+=100; ret--;} // im Grenzfall gerade Zahlen bevorzugen
if (frac) *frac=fra;
return ret;
}
// Startet Empfangs-Thread (unabhängig von »Empfang.Ein«)
static bool StartEmpfangOnce(void) {
switch (Config.Where) {
case 0: { // seriell AKA Win32
DWORD ThreadId;
TCHAR ComName[12];
wnsprintf(ComName,elemof(ComName),T("\\\\.\\COM%u"),Config.SerialNo+1);
hCom=CreateFile(ComName,GENERIC_READ|GENERIC_WRITE,
0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
if (hCom==INVALID_HANDLE_VALUE) hCom=0;
if (hCom) {
ClearSerialLines();
if (Config.SerialLineIn==4) { // via RxD
DCB dcb;
InitStruct(&dcb,sizeof(dcb));
dcb.BaudRate=1000/BITTIME; // hier: 50 Baud
// Dies ist die Einstellung des Programms „WinClk“ zur „Expert mouse clock“.
// Damit kann man keine zu langen Impulse detektieren.
// Die darin enthaltene Hardware (PIC16F84, abgeschliffen, aber ich erkenne es trotzdem)
// macht sich tatsächlich den Umstand, die LED „nacheilend“ blinken zu lassen.
// Somit ist es synchron zum Piep dieser Software.
// Zu bedenken beim Empfang via RxD ist,
// dass erst beim Einlauf eines Bytes (200 ms) die Länge gemessen
// und die Uhr gestellt werden kann. Dies wird in DecodeTime() kompensiert.
dcb.fBinary=1;
dcb.ByteSize=8; // alles andere bleibt Null
SetCommState(hCom,&dcb);
}
// positive Hilfsspannung
EscapeCommFunction(hCom,CodeFromLineOut[Config.SerialLineOut]);
SetCommMask(hCom,EventFromLineIn[Config.SerialLineIn]);
o.hEvent=CreateEvent(NULL,TRUE,TRUE,NULL);
hThread=CreateThread(NULL,0,UhrThread,NULL,0,&ThreadId);
// für genaue Zeitnahme Priorität hochsetzen
SetThreadPriority(hThread,THREAD_PRIORITY_HIGHEST);
return true;
}
}break;
case 1: { // parallel AKA Direktzugriff
hLibInOut=MyLoadLibrary(INPOUTDLL);
if (hLibInOut) { // "globale" Variablen vorbelegen
BYTE b;
(FARPROC)inportb=GetProcAddress(hLibInOut,"Inp32");
(FARPROC)outportb=GetProcAddress(hLibInOut,"Out32");
InAddr=Config.ParallelAddr+(Config.ParallelLineIn>>3);
InMask=1<<(Config.ParallelLineIn&7);
InInv=GetInvFromLine(Config.ParallelLineIn);
OutAddr=Config.ParallelAddr+(Config.ParallelLineOut>>3);
OutMask=1<<(Config.ParallelLineOut&7);
OutInv=GetInvFromLine(Config.ParallelLineOut);
OutSave=b=inportb(OutAddr);
if (OutInv) b&=~OutMask; else b|=OutMask;
outportb(OutAddr,b); // Stromversorgung einschalten
TimerPeriod=10;
timeBeginPeriod(TimerPeriod);
TimerId=timeSetEvent(TimerPeriod,TimerPeriod,TimerProc,0,TIME_PERIODIC);
return true;
}
}break;
case 2: { // Soundkarte und I/Q-Demodulator
// Das Wirkprinzip ist, das eingehende Frequenzgemisch in Blöcken von 25..50 ms
// entsprechend 20..40 Hz aufzuteilen und in jedem eine Korrelation mit einer
// Sinus- und einer Kosinusfunktion der gegebenen Frequenz zu machen.
// Heraus kommen 2 Zahlen, die man als komplexe Zahl interpretieren kann.
// Dessen Betrag ist ein Maß für die Amplitude der gewünschten Frequenz,
// dessen Argument (aka Winkel) die Phasenlage.
// Die Phasenlage bezüglich Blockanfang kann man nachher korrigieren,
// um das stetige Neuberechnen der Sinuswerte zu vermeiden.
// Die Bandbreite dieser Filterung ist durch die Stückelung vorgegeben: 10..20 Hz.
int i,size;
WAVEFORMATEX wf;
wf.wFormatTag=WAVE_FORMAT_PCM;
wf.nChannels=1;
wf.wBitsPerSample=16;
wf.nBlockAlign=2;
switch (Config.iSampleRate) {
case 11:
case 22:
case 44: demod.SamplesPerSec=MulDiv(Config.iSampleRate,44100,44); break;
default: demod.SamplesPerSec=Config.iSampleRate*1000;
}
wf.nSamplesPerSec=demod.SamplesPerSec;
wf.nAvgBytesPerSec=wf.nSamplesPerSec*wf.nBlockAlign;
if (i=waveInOpen(&demod.hWaveIn,Config.iSoundCard,&wf,(DWORD_PTR)waveInProc,0,CALLBACK_FUNCTION)) {
DbgPrintf(("waveInOpen Fehler %d\n",i));
return false;
}
_BitScanReverse(&size,wf.nSamplesPerSec/20);
DemodIQ.FFT_LenShift=(BYTE)size;
size=wf.nBlockAlign<<size; // sollte eine Zeit zwischen 50 und 100 ms ergeben!
DbgPrintf(("WaveIn-Blockgröße: %d Samples = %d ms\n",size>>1,MulDiv(size,500,wf.nSamplesPerSec)));
// minimal 512, maximal 16K, typisch 4K Samples pro Block (@ 8, 200, 44,1 kSa/s)
for (i=0; i<elemof(demod.wh); i++) {
WAVEHDR *wh=demod.wh+i;
if (!(wh->lpData=LocalAlloc(LMEM_FIXED,size))) return false;
wh->dwBufferLength=size;
if (waveInPrepareHeader(demod.hWaveIn,wh,sizeof(WAVEHDR))) return false;
if (waveInAddBuffer(demod.hWaveIn,wh,sizeof(WAVEHDR))) return false;
}
SetFiltFreq(Config.iFiltFreq*200+Config.iAFC);
demod.sintab=GenSintab();
demod.w=DemodIQ.nBlocks=0;
SetTrig();
//#ifdef _DEBUG
// QueryPerformanceFrequency(&demod.qpf);
//#endif
if (waveInStart(demod.hWaveIn)) return false;
return true;
}break;
case 3: { // Joystick-Feuerknopf
// wine BUG: Workaround: uPeriod==0 nicht erlaubt, daher == 10
switch (joySetCapture(MainWnd,Config.iJoystick,10,FALSE)) {
case 0: joyReleaseCapture(Config.iJoystick); goto joystart;
// Dieser Fehlerkode kommt regulär unter Win98SE!
// Auch die „Spieloptionen“ in der Systemsteuerung hat damit ein Problem.
// Offenbar erwartet Win98 zwingend einen Hebel für die Funktion.
// joyGetPosEx() funktioniert aber trotzdem.
case JOYERR_UNPLUGGED: if (LOBYTE(WinVer)<5) goto joystart; else break;
case JOYERR_NOCANDO: { // Win7-64: Kommt vor, trotzdem alles OK
joystart:
TimerPeriod=Config.iJoyButton&0xE0?20:10; // Zeitstempel vom Hebel: Nicht so straff abfragen
timeBeginPeriod(TimerPeriod);
TimerId=timeSetEvent(TimerPeriod,TimerPeriod,JoyTimerProc,0,TIME_PERIODIC);
JoyCapP1=Config.iJoystick+1; // merke den "gekaperten" Joystick
}return true;
}
}break;
case 4: { // UsbPrn
hCom=OpenDevInterface(&GUID_DEVINTERFACE_USBPRINT,Config.iUsbPrn,0,0);
if (hCom) {
o.hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
TimerPeriod=10;
timeBeginPeriod(TimerPeriod);
TimerId=timeSetEvent(TimerPeriod,TimerPeriod,UsbPrnTimerProc,0,TIME_PERIODIC);
return true;
}
}break;
case 5: { // Vorlaufempfänger
// Im Prinzip genauso wie der Joystick-Feuerknopf, nur dass Empfangsdaten
// oder die ermittelte Uhrzeit gepuffert und nachträglich abgeholt werden kann.
// Es wird nur genau 1 angestecktes Gerät unterstützt.
if (HidOpen(&gHid)) {
DWORD ThreadId;
__stosd((DWORD*)&o,0,sizeof o/4);
o.hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
hThread=CreateThread(NULL,0,HidThread,NULL,0,&ThreadId);
// für genaue Zeitnahme Priorität hochsetzen
//SetThreadPriority(hThread,THREAD_PRIORITY_ABOVE_NORMAL);
return true;
}
}break;
}
return false;
}
// wie StartEmpfangOnce, aber mit mehreren Versuchen nach Exe-Start oder Power-Event
bool StartEmpfang(void) {
while (!StartEmpfangOnce()) {
if (!TryReOpen) {
DbgPrintf(("StartEmpfang() versagt!\n"));
return false;
}
--TryReOpen;
Sleep(50);
}
TryReOpen=0; // nunmehr erfolgreich; keine Restarts bei thread-innerem Versagen
return true;
}
// Beendet Empfangs-Thread (unabhängig von »Empfang.Ein« und Config.Where)
void EndeEmpfang(void) {
if (demod.hWaveIn) {
int i;
waveInReset(demod.hWaveIn);
Sleep(1); // Ob das hilft??
for (i=0; i<elemof(demod.wh); i++) {
WAVEHDR *wh=demod.wh+i;
waveInUnprepareHeader(demod.hWaveIn,wh,sizeof(WAVEHDR));
LocalFree(wh->lpData);
}
waveInClose(demod.hWaveIn);
LocalFree(demod.sintab);
__stosd((void*)&demod,0,sizeof(demod)/4);
if (DemodIQ.FFT_Data) DemodIQ.FFT_Data=LocalFree(DemodIQ.FFT_Data);
}
if (hCom) SetCommMask(hCom,0); // WaitCommEvent() loseisen
if (hThread) { // seriell AKA Win32
if (o.hEvent) SetEvent(o.hEvent); // blockierende Abfrage beenden
if (WaitForSingleObject(hThread,3000)==WAIT_TIMEOUT)
TerminateThread(hThread,0);
if (Config.Piep) StopBeep();
CloseHandle(hThread);
hThread=0;
if (hCom) {
if (o.hEvent) CloseHandle(o.hEvent),o.hEvent=0; // USB-Modus?
else ClearSerialLines(); // Stromversorgung ausschalten
CloseHandle(hCom);
hCom=0;
}
}
if (hLibInOut) { // parallel AKA Direktzugriff
if (Config.Piep) StopBeep();
outportb(OutAddr,OutSave); // Stromversorgung ausschalten
}
if (TimerId) { // parallel oder Joystick
timeKillEvent(TimerId);
timeEndPeriod(TimerPeriod);
TimerId=0;
}
if (JoyCapP1) { // Joystick
joyReleaseCapture(JoyCapP1-1);
JoyCapP1=0;
}
if (hLibInOut) { // parallel AKA Direktzugriff
FreeLibrary(hLibInOut);
hLibInOut=0;
}
HidClose(&gHid);
}
// Thread-Neustart, NUR wenn »Empfang.Ein«!
bool RestartEmpfang(void) {
if (!Empfang.Ein) return true;
EndeEmpfang();
return StartEmpfang();
}
#undef hCom
bool HidOpen(THid*Hid) {
GUID hidGuid;
HidD_GetHidGuid(&hidGuid);
Hid->hDev=OpenDevInterface(&hidGuid,Config.iUsbHid,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE);
if (Hid->hDev) {
if (HidD_GetPreparsedData(Hid->hDev,&Hid->pd)) {
HidP_GetCaps(Hid->pd,&Hid->caps);
return true;
}
CloseHandle(Hid->hDev);
Hid->hDev=0;
}
return false;
}
void HidClose(THid*Hid) {
if (Hid->pd) {
HidD_FreePreparsedData(Hid->pd);
Hid->pd=0;
}
if (Hid->hDev) {
CloseHandle(Hid->hDev);
Hid->hDev=0;
}
}
Detected encoding: ANSI (CP1252) | 4
|
|