Source file: /~heha/hsn/AD9834.zip/src/Sweep.c

/*****************************************
 * Modeless Sweep Dialog for AD9834 Demo *
 *****************************************/

/* Der Dialog ist so gestaltet, dass alle Änderungen für den nächsten Sweep-Zyklus
 * automatisch wirksam werden! Auch kann im Hauptfenster weiter bspw. an der anderen Frequenz
 * oder allen anderen Schaltern herumgespielt werden.
 * Einzig die Frequenzeingabe ist beim Hauptdialog nicht mehr möglich.
 */
#define WIN32_LEAN_AND_MEAN
#include <windowsx.h>
#include "AD9834.h"
#include <commctrl.h>

#include <math.h>	// exp(), log()
#include <stdio.h>	// _snprintf()
#include <tchar.h>	// _sntprintf()

#define UPDATERATE 20	// Hz Aktualisierung von Zahlenausgaben
#define UPDATEPERF 4	// Hz Aktualisierung der Performance-Angabe

HWND hSweep;		// Dialog-Fensterhandle
static volatile bool running;	// Läuft der Thread? Oder soll er beendet werden? (Schreibzugriff nur durch Fenster-Thread!)
HANDLE hThread;
HANDLE hMutex;		// Verriegelung des Hardware-Zugriffs 

typedef struct{		// Daten für _einen_ Sweep (ich erspare mir den Hickhack mit LARGE_INTEGER, __allmul usw.)
 double f[2];		// Start- und Differenzfrequenz (Stopp-Start)
 double qpf;		// Ergebnis von QueryPerformanceFrequency()
 LARGE_INTEGER tic;	// Startzeitpunkt eines Sweep
 double toc;		// Gesamtlänge in s
 double t;		// aktuelle Zeit in s
 double tpost;		// Zeit für nächstes PostMessage();
 BYTE swflag;		// Bit 0: f1, Bit 1: logarithmisch, Bit 3: Dreieck
 BYTE m_dis;		// Disable/Enable-Flags: 0: Disable f0, 1: Disable f1, 2: Enable f0, 3: Enable f1
 struct{
  DWORD f;		// aus Config.freq
 }backup;
 DWORD m;		// Aktualisierungsmaske (für UpdateValues())
 struct{
  DWORD count;		// Anzahl Aktualisierungen
  DWORD tic;		// Startzeitpunkt des Threads
  DWORD toc;		// Nächste Aktualisierung
 }perf; 
}SWEEPDATA;

static void SaveBackup(SWEEPDATA*sd) {
 int i=(sd->swflag=Config.swflag)&1;
 sd->backup.f=Config.freq[i];
 sd->m_dis|=1<<i;	// Disable f0 oder f1
}
static void WriteFreq(SWEEPDATA*sd,DWORD f) {
 int i=sd->swflag&1;
 BYTE m=4<<i;
 if (Config.freq[i]==f) return;
 if ((Config.freq[i]^f)>>14) m|=2;	// High-Teil ändert sich?
 Config.freq[i]=f;
 UpdateChip(m);		// sofort ausgeben (blockiert etwas je nach Schnittstelle)
 sd->perf.count++;
 sd->m|=i?3<<18:3<<16;	// beim nächsten Mal (also etwas verzögert) anzeigen lassen
}
static void RestoreBackup(SWEEPDATA*sd) {
 int i=sd->swflag&1;
 sd->m_dis|=4<<i;	// Enable f0 oder f1
 WriteFreq(sd,sd->backup.f);
}

static void logarithmize(double*f) {
 *f=log(*f?*f:1E-3);	// (Frequenzen dürfen nicht Null sein!! Start dann mit 1 mHz)
}

static DWORD WINAPI ThreadProc(LPVOID unused) {
 SWEEPDATA sd;
 LARGE_INTEGER h;	// Hilfsvariable
 sd.m=0;
 sd.m_dis=0;
 SaveBackup(&sd);
 QueryPerformanceFrequency(&h);
 sd.qpf=(double)h.QuadPart;
 sd.perf.count=0;
 sd.perf.tic=GetTickCount();
 sd.perf.toc=sd.perf.tic+1000/UPDATEPERF;
 do{
// 1. Daten aud Config-Struktur kopieren und umrechnen (unsauber, wirklich Mutex einsetzen?)
  sd.f[0]=Config.sweep[0];
  sd.f[1]=Config.sweep[1];
  if ((sd.swflag^Config.swflag)&1) {	// Kanalzuordnung ändert sich mittendrin?
   RestoreBackup(&sd);		// Backup austauschen
   SaveBackup(&sd);
  }
  sd.swflag=Config.swflag;
  sd.toc=Config.swtime/E10(2);
  sd.tpost=0;
  if (sd.toc && sd.f[0]!=sd.f[1]) {	// Sinnvoller Sweep?
   double f,t;
   if (sd.swflag&1<<1) {	// logarithmisch?
    logarithmize(sd.f+0);	// Logarithmen ablegen
    logarithmize(sd.f+1);
   }
   sd.f[1]-=sd.f[0];
   QueryPerformanceCounter(&sd.tic);
// 2. Sweep ausführen und dabei Abbruch prüfen
   do{
    DWORD gtc;
    QueryPerformanceCounter(&h);
    sd.t=(h.QuadPart-sd.tic.QuadPart)/sd.qpf;
    t=sd.t/sd.toc;		// relative Steigung (der Zeitachse) 0..1
    if (t>1) t=1;		// begrenzen (selbst wenn's nicht ganz passt)
    SetTrigger(t<0.5);		// Triggersignal (etwa 1:1) für Wobbeleinrichtung mit ausgeben
    if (sd.swflag&1<<3) {	// Dreieck?
     t*=2;			// doppelte Steigung
     if (t>=1) t=2-t;		// fallend? - doppelte Steigung, aber fallend
    }
    f=sd.f[0]+sd.f[1]*t;	// Frequenz (oder dessen Logarithmus) linear ausrechnen
    if (sd.swflag&1<<1) f=exp(f);// logarithmisch? - jetzt entlogarithmieren
#ifdef _DEBUG
    {
     char s[64];
     _snprintf(s,elemof(s),"t=%lf, f=%lf, bits=0x%07X\r\n",t,f,FromHz(f));
     OutputDebugStringA(s);
    }
#endif
    WriteFreq(&sd,FromHz(f));
    if (sd.t>=sd.tpost) {
     if (sd.m_dis) PostMessage(hSweep,WM_USER,WM_ENABLE,sd.m_dis);
     sd.m_dis=0;
     if (sd.m) PostMessage(hSweep,WM_USER,WM_NOTIFY,sd.m);	// Anzeigen nachführen
     sd.m=0;
     sd.tpost=sd.t+1.0/UPDATERATE;	// Aktualisierungsrate
    }
    gtc=GetTickCount();
    if ((int)(gtc-sd.perf.toc)>0) {
     PostMessage(hSweep,WM_USER,WM_SETTEXT,MulDiv(sd.perf.count,1000,gtc-sd.perf.tic));
     sd.perf.toc=gtc+1000/UPDATEPERF;
     gtc-=sd.perf.tic;		// Differenz in ms
     if (gtc>10000) {		// 10 s vergangen?
      sd.perf.count=MulDiv(sd.perf.count,gtc-1000,gtc);	// einen Teil, 1 s entsprechend, abschneiden
      sd.perf.tic+=1000;	// 1 s abschneiden
     }
    }
    Sleep(0);		// Dieser Thread verbrät tatsächlich 100 % Rechenlast!
    if (!running) goto raus;
   }while (sd.t<sd.toc);
  }else{		// bei Zeitangabe Null oder zwei gleichen Frequenzen einfach Festfrequenz ausgeben
   DWORD tic=GetTickCount();
   WriteFreq(&sd,FromHz(sd.f[0]));
   if (sd.m_dis) PostMessage(hSweep,WM_USER,WM_ENABLE,sd.m_dis);
   sd.m_dis=0;
   if (sd.m) {
    PostMessage(hSweep,WM_USER,WM_NOTIFY,sd.m);	// Anzeigen nachführen
    PostMessage(hSweep,WM_USER,WM_SETTEXT,0);
   }
   sd.m=0;
   do{
    Sleep(1000/UPDATERATE);
    if (!running) goto raus;
   }while (GetTickCount()-tic<(DWORD)(sd.toc*1000));	// für den Fall längerer Sweep-Zeiten: kreiseln vor Ende (One-Shot)
  }
// 3. Ende (bei "Einfach") oder zurück zu 1. (bei "Wiederholt")
 }while (Config.swflag&1<<2);		// Abbruch bei Einzel-Sweep (direkt, nicht aus Kopie lesen)
raus:
 SetTrigger(false);
 RestoreBackup(&sd);
 if (sd.m) PostMessage(hSweep,WM_USER,WM_NOTIFY,sd.m);	// Anzeigen zurückstellen
 PostMessage(hSweep,WM_USER,WM_ENABLE,sd.m_dis);
 PostMessage(hSweep,WM_USER,WM_QUIT,0);	// (Recycling von WM-Nachrichten)
 return 0;
}

// Aufruf wenn Editfenster geändert (nach 500 ms, dir==0)
// oder beim Betätigen einer Scrollfunktion (sofort, dir!=0)
// Zulässige IDs: 16..18
static bool HandleEditChange(BYTE id, int dir) {
 char s[32];
 double f,fv,fs,add=dir;
 int nk=3,dp,a,e;
 GetDlgItemTextA(hSweep,id,s,elemof(s));
 if (!*s) return false;
 SendDlgItemMessage(hSweep,id,EM_GETSEL,(WPARAM)&a,(LPARAM)&e);
 dp=StringToFloat(s,&f);
 if (!dp) return false;
 if (a || e!=lstrlen(s)) {	// Bei Vollmarkierung bei Einerfrequenzen bleiben
  int pot=dp-e;
  if (pot<0) add/=E10(-1-pot);	// -1: Dezimalpunkt als solchen ignorieren
  else add*=E10(pot);
 }else if (id==18) add/=10;	// Zehntelsekunden als Vorgabe
 fv=f;				// Wert vorher
 switch (id) {
  case 16:
  case 17: {		// Frequenz-Angaben
   int i=id-16;
   f+=add;
   if (f<0) {
    if (dir) f=0;			// begrenzen
    if (f==fv) return false;		// unverändert: Piep!
   }
   fs=E10(Config.swunit);
   f*=fs;
   fv*=fs;				// in Hz umrechnen
   if (f>=Config.mclk) {
    if (dir) f=Config.mclk-1;		// 1 Hz drunter
    if (f==fv) return false;		// unverändert: Piep!
   }
   Config.sweep[i]=(float)f;
  }break;

  case 18: {		// Zeit (Hundertstel Sekunden)
   fs=1;
   f+=add;
   if (f<0) {
    if (dir) f=0;			// begrenzen
    if (f==fv) return false;		// unverändert: Piep!
   }
   if (f>=655.35) {
    if (dir) f=655.35;			// begrenzen
    if (f==fv) return false;		// unverändert: Piep!
   }
   Config.swtime=(WORD)(f*100);		// Hundertstelsekunden speichern
   nk=2;				// Hundertstel anzeigen
  }break;
 }
 if (dir) FloatToEdit(f/fs,nk,hSweep,id);
 return true;
}

static void HandleScroll(HWND Wnd, int dir) {
 BYTE id=(BYTE)GetWindowID(Wnd);
 if (GetKeyState(VK_CONTROL)<0) dir*=10;	// Schrittweite bei Strg-Taste potenzieren
 if (!HandleEditChange(id,dir))
   MessageBeep(MB_ICONEXCLAMATION);
}

static WNDPROC DefEditProc;

static LRESULT CALLBACK UpDownEditProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
 switch (Msg) {
  case EM_SETSEL: {
   if (!wParam && (long)lParam==-1) return 0;
  }break;
  case WM_VSCROLL: switch (LOWORD(wParam)) {
   case SB_LINEUP:	HandleScroll(Wnd,1); break;
   case SB_LINEDOWN:	HandleScroll(Wnd,-1); break;
   case SB_PAGEUP:	HandleScroll(Wnd,10); break;
   case SB_PAGEDOWN:	HandleScroll(Wnd,-10); break;
  }break;
  case WM_KEYDOWN: switch (wParam) {
   case VK_UP:		HandleScroll(Wnd,1); return 0;	// Taste verschlucken
   case VK_DOWN:	HandleScroll(Wnd,-1); return 0;
   case VK_PRIOR:	HandleScroll(Wnd,10); return 0;
   case VK_NEXT:	HandleScroll(Wnd,-10); return 0;
  }break;
 }
 return CallWindowProc(DefEditProc,Wnd,Msg,wParam,lParam);
}

static void SweepUpdateValues(BYTE m) {
// Schalter
 if (m&1<<0) CheckRadioButton(hSweep,64,65,64+(Config.swflag&1));
 if (m&1<<1) CheckRadioButton(hSweep,66,67,66+((Config.swflag>>1)&1));
 if (m&1<<2) CheckRadioButton(hSweep,68,69,68+((Config.swflag>>2)&1));
 if (m&1<<3) CheckDlgButton(hSweep,71,(Config.swflag>>3)&1);
// Eckfrequenzen
 if (m&1<<4) FloatToEdit(Config.sweep[0]/E10(Config.swunit),3,hSweep,16);
 if (m&1<<5) FloatToEdit(Config.sweep[1]/E10(Config.swunit),3,hSweep,17);
// Zeit
 if (m&1<<6) FloatToEdit(Config.swtime/E10(2),2,hSweep,18);
// Frequenzeinheit
 if (m&1<<7) CheckRadioButton(hSweep,32,38,32+Config.swunit);
}

// Zwei Editfenster ein/ausschalten
static void enab(HWND w, UINT id, BOOL ena) {
 EnableWindow(GetDlgItem(w,id),ena);
 EnableWindow(GetDlgItem(w,id+1),ena);
}

// Anstelle einer Hauptschleife mit GetMessage() usw., hier ein Hook.
// Sonst fehlt dem nichtmodalen Fenster ein Tastaturinterface.
static HHOOK gmh;
static LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam) {
 LPMSG m=(LPMSG)lParam;
 if (code==HC_ACTION && m->message>=WM_KEYFIRST && m->message<=WM_KEYLAST) {
  if (m->message==WM_KEYDOWN && m->wParam==VK_F6) {
   SetFocus(hMainWnd);		// irgendwie Fokus zurück (Windows bietet nichts)
  }else if (IsDialogMessage(hSweep,m)) m->message=WM_NULL;	// Geht nicht anders!!
 }
 return CallNextHookEx(gmh,code,wParam,lParam);
}

INT_PTR CALLBACK SweepDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
 switch (Msg) {
  case WM_INITDIALOG: {
   int i;
   hSweep=Wnd;
   for (i=16; i<=18; i++) {
    HWND hEdit=GetDlgItem(Wnd,i);
    DefEditProc=SubclassWindow(hEdit,UpDownEditProc);
    CreateUpDownControl(WS_VISIBLE|WS_CHILD|UDS_ALIGNRIGHT|UDS_HOTTRACK,0,0,0,0,Wnd,-1,hInstance,hEdit,100,-100,0);
   }
   SweepUpdateValues((BYTE)-1);
  }return TRUE;

  case WM_ACTIVATE: if (LOBYTE(wParam)) {
   gmh=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,NULL,GetCurrentThreadId());
  }else{
   UnhookWindowsHookEx(gmh);
  }break;

  case WM_COMMAND: switch (LOBYTE(wParam)) {
   case 16:
   case 17:
   case 18: {
    if (HIWORD(wParam)==EN_CHANGE) SetTimer(Wnd,LOBYTE(wParam),500,NULL);
   }break;
   case 32+0:
   case 32+3:
   case 32+6: {
    Config.swunit=LOBYTE(wParam)-32;
    SweepUpdateValues(3<<4);		// Beide Frequenzangaben aktualisieren
   }break;
   case 64: Config.swflag&=~(1<<0); break;
   case 65: Config.swflag|=1<<0; break;
   case 66: Config.swflag&=~(1<<1); break;
   case 67: Config.swflag|=1<<1; break;
   case 68: Config.swflag&=~(1<<2); break;
   case 69: Config.swflag|=1<<2; break;
   case 71: if (IsDlgButtonChecked(Wnd,wParam)) Config.swflag|=1<<3; else Config.swflag&=~(1<<3); break;

   case IDOK: {		// Run/Stop
    TCHAR s[32];
    if (!running && Config.swflag&1<<1 && (!Config.sweep[0] || !Config.sweep[1])) {
     HWND w=GetDlgItem(Wnd,Config.sweep[0]?17:16);
     SetFocus(w);
     Edit_SetSel(w,0,-1);
     MBox(Wnd,2,MB_OK);	// Logarithmisch geht nur mit Eckfrequenzen ungleich Null
     break;
    }
    running=!running;
    LoadString(hInstance,4+running,s,elemof(s));
    SetDlgItemText(Wnd,1,s);	// Beschriftung wechseln
    if (running) {
     DWORD id,affinity,possiblemask=1<<nInstance;
     hMutex=CreateMutex(NULL,FALSE,NULL);
     hThread=CreateThread(NULL,0,ThreadProc,NULL,0,&id);
     GetProcessAffinityMask(GetCurrentProcess(),&affinity,&id);
     if (affinity&possiblemask)		// Thread an einen Prozessor? (aber für jede Instanz einen anderen!)
       SetThreadAffinityMask(hThread,possiblemask);	// für korrekte Funktion von QueryPerformanceCounter()
    }else{
     WaitForSingleObject(hThread,INFINITE);
     CloseHandle(hThread);
     hThread=0;
     CloseHandle(hMutex);
     hMutex=0;
     SetDlgItemText(Wnd,10,T(""));
    }
   }break;

   case IDCANCEL: {
    if (running) {
     SendMessage(Wnd,Msg,IDOK,0);
     PostMessage(Wnd,Msg,IDCANCEL,0);	// erst WM_USER-Nachrichten verarbeiten, erst dann Fenster schließen
    }else DestroyWindow(Wnd);
   }break;
  }break;

  case WM_TIMER: {
   KillTimer(Wnd,wParam);
   HandleEditChange(LOBYTE(wParam),0);
  }break;

  case WM_NOTIFY: {
   if (HandleUpDownNotify(Wnd,lParam)) return TRUE;
  }break;

  case WM_USER: switch (wParam) {	// Thread informiert Fenster
   case WM_QUIT: {			// Thread beendet sich
    if (running)			// Wenn nicht durch Anwender verursacht ...
      SendMessage(Wnd,WM_COMMAND,IDOK,0);
   }break;
   case WM_NOTIFY: {			// Thread wünscht Aktualisierung von Dialogelementen (alle 100 ms)
    UpdateValues(lParam);
   }break;
   case WM_ENABLE: {			// Thread wünscht Ein/Ausschalten der Frequenzeingabe im Hauptdialog
    if (lParam&1) enab(hMainWnd,16,FALSE);
    if (lParam&2) enab(hMainWnd,18,FALSE);
    if (lParam&4) enab(hMainWnd,16,TRUE);
    if (lParam&8) enab(hMainWnd,18,TRUE);
   }break;
   case WM_SETTEXT: {
    if (lParam) {
     TCHAR s[32],t[32];
     LoadString(hInstance,6,t,elemof(t));	// "Ausgaberate: %u /s"
     _sntprintf(s,elemof(s),t,lParam);
     GetDlgItemText(Wnd,10,t,elemof(t));
     if (lstrcmp(s,t)) SetDlgItemText(Wnd,10,s);
    }else SetDlgItemText(Wnd,10,T(""));		// löschen (bei Zeit-Null oder f0==f1)
   }break;
  }break;

  case WM_DESTROY: {
   if (running) SendMessage(Wnd,WM_COMMAND,IDOK,0);
   hSweep=0;
  }break;
 }
 return FALSE;
}
Detected encoding: UTF-80