/*****************************************
* 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-8 | 0
|