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

/* Evaluation software for Analog Devices AD9834 DDS generator
 * (Direct Digital Synthesis) for nearly random frequencies.
 * Replaces the silly Analog Devices software.
 *
 *
 * Siehe http://www.tu-chemnitz.de/~heha/hs_freeware/mein_msvc.htm
 *
 * Änderungen (~=Bugfix, +=neues Feature, -=Feature entfernt, *=Änderung)
 100701	Erstausgabe
~100803	Kleinkram, Icon (leider ohne Wirkung)
+100814	Sweep-Funktion
-140505	COM1..256 per SetupDi-Funktionen; Bruch mit Windows 95.
	Einstellungen nach HKEY_CURRENT_USER
	UpDownControl-Verhalten mit Cursor (d.h. Schrittweite einstellbar)
+140507	Echte UpDownControls (sehen unter Windows 7 besser aus)
	Umstellung float->double (sonst lästige Rundungsfehler)
 *
 * TODO
+	DDE-Interface
 */
#define WIN32_LEAN_AND_MEAN
#include <windowsx.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <setupapi.h>
#include <devguid.h>
//#include "../gnul/gnul.h"
#include "AD9834.h"

#include <stdio.h>
#include <tchar.h>


void _fastcall InitStruct(void*p, size_t len) {
 __stosd(p,0,len>>2);
 *(size_t*)p=len;
}

TCHAR MBoxTitle[64];

int vMBox(HWND Wnd, UINT id, UINT style, va_list arglist) {
 TCHAR buf1[256],buf2[1024];
 LoadString(0,id,buf1,elemof(buf1));
 wvnsprintf(buf2,elemof(buf2),buf1,arglist);
 return MessageBox(Wnd,buf2,MBoxTitle,style);
}

int _cdecl MBox(HWND Wnd, UINT id, UINT style,...) {
 return vMBox(Wnd,id,style,(va_list)(&style+1));
}

static void EnableDlgItem(HWND w, UINT id, BOOL ena) {
 EnableWindow(GetDlgItem(w,id),ena);
}

HINSTANCE hInstance;		// Quelle der Ressourcen
int nInstance;			// Laufende Instanz eines AD9834-Fensters, 0..31
HWND hMainWnd;
static DWORD mInstances;	// Beim Start bereits vorgefundene Instanzen
static char sDecimal[4];	// Set, 1. Zeichen je nach Windows-Einstellung
EXTERN_C void _declspec(naked) _cdecl _fltused() {}	// Linker ruhig stellen


// All constant Unicode strings are listed here for conversion by GNUL
const LPCTSTR S[]={
 T("Software\\h#s"),
 T("AD9834"),
 T("Config%d"),
 T("%Xh (%u, LPT%u)"),	// [3]
 T("PortName"),		// [4] Registry-Schlüssel
 T("AD9834.lng"),	// [5] LangFileName
 T("\\\\.\\COM%u"),	// [6] zum Öffnen
 T("%03Xh"),
 T("UsbPrn%u"),		// [8]
 NULL};

#define LangFileName S[5]
#define sizeofTCHAR sizeof(TCHAR)

TConfig Config;

// Lädt Konfiguration. Bei Erfolg sollte Config.iomode != 0 sein.
static void LoadSettings(void) {
 HKEY hKey0,hKey1;
 TCHAR n[16];
 DWORD l;
// Schlüssel-Zweig stufenweise öffnen
 if (!RegOpenKeyEx(HKEY_CURRENT_USER,S[0],0,KEY_QUERY_VALUE,&hKey0)) {
  if (!RegOpenKeyEx(hKey0,S[1],0,KEY_QUERY_VALUE,&hKey1)) {
   wnsprintf(n,elemof(n),S[2],nInstance);
   l=sizeof(Config);
   RegQueryValueEx(hKey1,n,NULL,NULL,(PBYTE)&Config,&l);
   RegCloseKey(hKey1);
  }
  RegCloseKey(hKey0);
 }
}

// Speichert Konfiguration. Die Fensterposition sollte bereits eingesetzt sein.
static void SaveSettings(void) {
 HKEY hKey0, hKey1;
 DWORD dispo;
 TCHAR n[16];
 TCHAR v[64];
 int l;
// Schlüssel-Zweig stufenweise erzeugen
 if (!RegCreateKeyEx(HKEY_CURRENT_USER,S[0],0,
   NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&hKey0,&dispo)) {
  if (dispo==REG_CREATED_NEW_KEY) {
   l=LoadString(0,0,v,elemof(v));		// Beschreibung: Hersteller
   RegSetValueEx(hKey0,NULL,0,REG_SZ,(PBYTE)v,(l+1)*sizeofTCHAR);
  }
  if (!RegCreateKeyEx(hKey0,S[1],0,
   NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&hKey1,&dispo)) {
   if (dispo==REG_CREATED_NEW_KEY) {
    l=LoadString(hInstance,1,v,elemof(v));	// Beschreibung: Produkt
    RegSetValueEx(hKey1,NULL,0,REG_SZ,(PBYTE)v,(l+1)*sizeofTCHAR);
   }
   wnsprintf(n,elemof(n),S[2],nInstance);
   RegSetValueEx(hKey1,n,0,REG_BINARY,(PBYTE)&Config,sizeof(Config));
   RegCloseKey(hKey1);
  }
  RegCloseKey(hKey0);
 }
}

/*******************
 * Einstell-Dialog *
 *******************/
static const WORD DefAddrs[]={0x378,0x278,0x3BC};

INT_PTR CALLBACK SetupDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
 switch (Msg) {
  case WM_INITDIALOG: {
// Parallele Schnittstellen listen
   int i;
   bool set=false;
   HWND w=GetDlgItem(Wnd,15);
   TCHAR buf[20];
   for (i=0; i<elemof(DefAddrs); i++) {
    wnsprintf(buf,elemof(buf),S[3],DefAddrs[i],DefAddrs[i],i+1);
    ComboBox_AddString(w,buf);
    if (Config.paradr==DefAddrs[i]) {
     ComboBox_SetCurSel(w,i);
     set=true;
    }
   }
   if (!set) {
    wnsprintf(buf,elemof(buf),S[7],Config.paradr);
    SetWindowText(w,buf);
   }
   CheckRadioButton(Wnd,10,13,9+Config.iomode);
   SendMessage(Wnd,WM_COMMAND,10,0);
   SendMessage(Wnd,WM_TIMER,1,0);
  }return TRUE;

  case WM_DEVICECHANGE: SetTimer(Wnd,1,2500,NULL); break;

  case WM_TIMER: if (wParam==1) {
   HWND w;
   UINT i,j;
   DWORD NeedBytes=0;
   HANDLE devs;
   KillTimer(Wnd,wParam);
// serielle Schnittstellen (neu) listen (bei jedem WM_DEVICECHANGE bspw. für USB)
   w=GetDlgItem(Wnd,14);
   ComboBox_ResetContent(w);
   devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,NULL,0,DIGCF_PRESENT);
   if (devs!=INVALID_HANDLE_VALUE) {
    SP_DEVINFO_DATA devInfo;
    devInfo.cbSize=sizeof devInfo;
    for (i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
     HKEY hKey;
     char s[16];	// Kann ANSI oder Unicode sein!
     DWORD slen=sizeof s;
     *s=0;
     if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ))
       ==INVALID_HANDLE_VALUE) continue;
     RegQueryValueEx(hKey,S[4],NULL,NULL,(LPBYTE)s,&slen);
     RegCloseKey(hKey);
     if (*s=='C') {
      int idx=ComboBox_AddString(w,(PTSTR)s);
      int num=StrToInt((PTSTR)s+3)-1;	// nullbasiert
      ComboBox_SetItemData(w,idx,num);
      if (num==Config.comnum) ComboBox_SetCurSel(w,idx);
     }
    }
    SetupDiDestroyDeviceInfoList(devs);
   }
// USB-Drucker-Adapter listen
   w=GetDlgItem(Wnd,16);
   ComboBox_ResetContent(w);
   j=GetNumUsbPorts();
   for (i=0; i<j; i++) {
    TCHAR UsbName[16];
    wnsprintf(UsbName,elemof(UsbName),S[8],i);
    ComboBox_AddString(w,UsbName);
    if (Config.u2pnum==i) ComboBox_SetCurSel(w,i);
   }
  }break;

  case WM_COMMAND: switch (LOWORD(wParam)) {

   case 10:
   case 11:
   case 12: {
    int i;
    for (i=10; i<=12; i++) EnableDlgItem(Wnd,i+4,IsDlgButtonChecked(Wnd,i));
   }break;

   case 9: {
    TCHAR fn[MAX_PATH];		// Textdatei
    LoadString(hInstance,3,fn,elemof(fn));
    ShellExecute(Wnd,NULL,fn,NULL,NULL,SW_SHOW);
   }break;

   case IDOK: {
    int i;
    if (IsDlgButtonChecked(Wnd,10)) {
     HWND w=GetDlgItem(Wnd,14);
     i=ComboBox_GetCurSel(w);
     if (i<0) {
      MBox(Wnd,17,MB_OK);
      SetFocus(w);
      break;	// nicht OK!
     }
     Config.comnum=(BYTE)ComboBox_GetItemData(w,i);
     Config.iomode=1;
    }else if (IsDlgButtonChecked(Wnd,11)) {
     HWND w=GetDlgItem(Wnd,15);
     char s[16];
     DWORD a;
     GetWindowTextA(w,s,sizeof(s));
     a=strtoul(s,NULL,16);
     if (a<0x100 || a>=0x10000) {
      MBox(Wnd,18,MB_OK);
      SetFocus(w);
      break;	// nicht OK!
     }
     Config.paradr=(WORD)a;
     Config.iomode=2;
    }else if (IsDlgButtonChecked(Wnd,12)) {
     HWND w=GetDlgItem(Wnd,16);
     i=ComboBox_GetCurSel(w);
     if (i<0) {
      MBox(Wnd,17,MB_OK);
      SetFocus(w);
      break;	// nicht OK!
     }
     Config.u2pnum=i;
     Config.iomode=3;
    }else{
     MBox(Wnd,16,MB_OK);
     SetFocus(GetDlgItem(Wnd,10));
     break;
    }
    if (hMutex) WaitForSingleObject(hMutex,INFINITE);
    SpiClose();
    i=SpiOpen(Wnd);
    ReleaseMutex(hMutex);
    if (!i) break;
   }nobreak;
   case IDCANCEL: {
    EndDialog(Wnd,LOWORD(wParam));
   }break;
  }break;
 }
 return FALSE;
}

static void SpiSendFreq(DWORD f, WORD m) {
 SpiSend((WORD)f&0x3FFF|m);
 f>>=14;
 SpiSend((WORD)f|m);
}

// (Serielle) Daten auf AD9834-Chip ausgeben
// m = Aktualisierungs-Maske
// Bit 0 = Control-Register
// Bit 1 = volle Frequenz-Aktualisierung
// Bit 2,3 = Frequenz-Register 0 und 1
// Bit 4,5 = Phasen-Register 0 und 1
// Bit 6 = Zustand der RESET-Leitung
// Zum Setzen der vollen Frequenz _muss_ vorher das Control-Register geschrieben werden,
// sonst "springt" die Frequenz zwischen Low- und High-Word.
// (Murks, siehe Datenblatt Seite 18/32)
/*** Aufruf von beiden Threads, per Mutex abgesichert ***/
void UpdateChip(BYTE m) {
 static WORD ctl;	// Zuletzt geschriebenes Control-Register
 if (hMutex) WaitForSingleObject(hMutex,INFINITE);
 if (m&3 || m&12 && ctl&0x2000) {
  ctl=Config.control;
  if (m&2) ctl|=0x2000;	// Mindestens eine volle Frequenz wird gesetzt
  SpiSend(ctl);
 }
 if (m&4) {
  if (m&2) SpiSendFreq(Config.freq[0],0x4000);
  else SpiSend((WORD)Config.freq[0]&0x3FFF|0x4000);
 }
 if (m&8) {
  if (m&2) SpiSendFreq(Config.freq[1],0x8000);
  else SpiSend((WORD)Config.freq[1]&0x3FFF|0x8000);
 }
 if (m&1<<4) SpiSend(Config.phase[0]|0xC000);
 if (m&1<<5) SpiSend(Config.phase[1]|0xE000);
 if (m&1<<6) SetResetPin();
 SpiFlush();
 if (hMutex) ReleaseMutex(hMutex);
}

// Hier: als Festkommawert ausgeben
static int FloatToString(double v, int nk, char*buf, int buflen) {
 int i=_snprintf(buf,buflen,"%.*f",nk,v);
 char*p=StrChrA(buf,'.');
 if (p) *p=*sDecimal;
 return i;
}

int StringToFloat(char*s, double*v) {
 char*p,*q;
 if (!s || !*s) return 0;
 p=StrPBrkA(s,sDecimal);
 if (p) *p='.';
 *v=strtod(s,&q);
 if (*q) return 0;	// OK nur wenn String-Ende erreicht
 return (p?p:q)-s;	// Index des Dezimaltrenners
}

/****************
 * Hauptfenster *
 ****************/

// Aktuelle Verbindungsart (links oben) anzeigen
static void ShowConn(void) {
 TCHAR s[32];
 DWORD p;
 PCTSTR t=S[5+Config.iomode];
 switch (Config.iomode) {
  case 1: p=Config.comnum+1; t+=4; break;
  case 2: p=Config.paradr; break;
  case 3: p=Config.u2pnum; break;
  default: return;
 }
 wnsprintf(s,elemof(s),t,p);
 SetDlgItemText(hMainWnd,126,s);
}

// Zwei sich ausschließende Radiobuttons schalten
static void CheckRadio(HWND w, int controlbitnr) {
 UINT id=128+(controlbitnr<<1);
 CheckRadioButton(w,id,id+1,id+((Config.control>>controlbitnr)&1));
}

static double ToHz(DWORD freq) {
 return Config.mclk/(1<<28)*freq;
}

// Liefert Skalierungswert des Einheitenpräfix' (nur "k" und "M")
double E10(int e) {
 double ret=1;
 if (e>0 && e<=32) do ret*=10; while(--e);
 return ret;
}

static double ScaleHz(DWORD freq) {
 return ToHz(freq)/E10(Config.unitpre);	// Hz (unverändert)
}

static double ScaleGrd(WORD pha) {
 return 360.0/(1<<12)*pha;	// Grad
}

// Edit-Text setzen, dabei die Auswirkungen von EN_CHANGE
// (nämlich den Timer) zunichte machen und Markierung (Kursorposition) rechtsbündig behalten
static void ChangeEdit(HWND w, UINT id, const char*s) {
 HWND ed=GetDlgItem(w,id);
 int a,e,grow;
 SendMessage(ed,EM_GETSEL,(WPARAM)&a,(LPARAM)&e);
 grow=lstrlen(s)-GetWindowTextLength(ed);
 if (a||!e) a+=grow;	// Markierung darf nach links wachsen / von links schrumpfen (Wenn Anfang=0 bleibt's 0)
 e+=grow;		// Ende stets rechtsbündig
 SetWindowTextA(ed,s);
 SendMessage(ed,EM_SETSEL,a,e);
 KillTimer(w,id);
}

void FloatToEdit(double v, int nk, HWND w, UINT id) {
 char s[32];
 FloatToString(v,nk,s,sizeof(s));
 ChangeEdit(w,id,s);
}

static void BitsToEdit(DWORD v, int radix, HWND w, UINT id) {
 char s[33];
 if (radix==16) _snprintf(s,sizeof(s),"%X",v);
 else _itoa(v,s,radix);
 ChangeEdit(w,id,s);
}

// Aktualisierung der Dialogelemente, NICHT der Hardware
void UpdateValues(DWORD m) {
 HWND w=hMainWnd;
// Bits
 if (m&1<<1) CheckRadio(w,1);	// MODE (Analogausgang)
 if (m&1<<3) {			// DIV2, DIGN/PIB, OPBITEN (Digitalausgang)
  static const BYTE cases[]={0,0,0,0,2,1,3,3};
  CheckRadioButton(w,48,51,48+cases[Config.control>>3&7]);
  EnableDlgItem(w,131,!(Config.control&(1<<5|1<<6)));	// Dreieck nicht auswählbar machen (reservierte Kombination)
 }
 if (m&1<<6) {			// SLEEP12
  BOOL ena=~Config.control&1<<6;
  CheckRadio(w,6);
  EnableDlgItem(w,45,ena);
  EnableDlgItem(w,130,ena);
  EnableDlgItem(w,131,!(Config.control&(1<<5|1<<6)));
 }
 if (m&1<<7) CheckRadio(w,7);	// SLEEP1
 if (m&1<<8) CheckRadio(w,8);	// RESET
 if (m&1<<9) {			// PIN/SW
  static const BYTE list[]={42,150,151,43,148,149,140,141,142,143,144,145,44};
  int i,e=elemof(list);
  BOOL ena=~Config.control&1<<9;
  if (Config.iomode>=2) e-=3;	// RESET kann weiter geschaltet werden!
  CheckRadio(w,9);
  for (i=0; i<e; i++) EnableDlgItem(w,list[i],ena);
 }
 if (m&1<<10) CheckRadio(w,10);
 if (m&1<<11) CheckRadio(w,11);
 if (m&1<<15) BitsToEdit(Config.control,2,w,15);
// Frequenzregister
 if (m&1<<16) FloatToEdit(ScaleHz(Config.freq[0]),3,w,16);
 if (m&1<<17) BitsToEdit(Config.freq[0],Config.radix,w,17);
 if (m&1<<18) FloatToEdit(ScaleHz(Config.freq[1]),3,w,18);
 if (m&1<<19) BitsToEdit(Config.freq[1],Config.radix,w,19);
// Phasenregister
 if (m&1<<20) FloatToEdit(ScaleGrd(Config.phase[0]),2,w,20);
 if (m&1<<21) BitsToEdit(Config.phase[0],Config.radix,w,21);
 if (m&1<<22) FloatToEdit(ScaleGrd(Config.phase[1]),2,w,22);
 if (m&1<<23) BitsToEdit(Config.phase[1],Config.radix,w,23);
//Speisefrequenz
 if (m&1<<24) FloatToEdit(Config.mclk/E10(6),4,w,24);
// Schalter
 if (m&1<<30) CheckRadioButton(w,32,38,32+Config.unitpre);
 if (m&1<<31) CheckRadioButton(w,80,96,80+Config.radix);
}

static void HandleBitButton(BYTE id) {
 BYTE bitno=(id-128)>>1;
 if ((id^Config.control>>bitno)&1) {	// wenn das Bit kippt ...
  DWORD mask=1<<bitno;
  Config.control^=mask;
  UpdateValues(mask|1<<15);
  UpdateChip(bitno==8 ? 0x41 : 1);	// ggf. mit RESET-Steuerung
 }
}

// aus Frequenz (in Hz) den Programmierwert (28 bit) berechnen
// Typ <double> nur für Sweep-Funktion (das Benutzerinterface benutzt durchweg <float>)
DWORD FromHz(double f) {
 DWORD d=(DWORD)(f*(1<<28)/Config.mclk+0.5);
 if (d>=1<<28) d=(1<<28)-1;		// kann beim Runden passieren
 return d;
}

// Aufruf wenn Editfenster geändert (nach 500 ms, dir==0)
// oder beim Betätigen einer Scrollfunktion (sofort, dir!=0)
// Zulässige IDs: 15..24
static bool HandleEditChange(BYTE id, int dir) {
 DWORD d;
 int i=id>>1&1;				// Index Frequenz- oder Phasenregister
 char s[32];
 int a,e,pot;
 GetDlgItemTextA(hMainWnd,id,s,elemof(s));
 if (!*s) return false;
 SendDlgItemMessage(hMainWnd,id,EM_GETSEL,(WPARAM)&a,(LPARAM)&e);
 if (id&1) {	// ungerade IDs sind Integer-Eingaben
  int radix=2;	// stets binär für Control-Bits
  char*p;
  DWORD dv;
  if (id>=16) radix=Config.radix;
  dv=d=strtoul(s,&p,radix);
  if (*p) return false;
  pot=lstrlen(s)-e;	// Anzahl Zeichen hinter Selektions-Ende
  if (pot) do dir*=radix; while (--pot);
  d+=dir;
  switch (id) {
   case 15: {		// Control-Bits
    if (d>=1<<12) return false;		// obere Bits (B28 und HLB) müssen hier 0 sein
    if (Config.control!=d) {
     BYTE m=1;
     if ((Config.control^d)&1<<8) m=0x41;	// wenn sich das RESET-Bit ändert
     Config.control=(WORD)d;
     UpdateValues(0x7FFF);		// (einfach) alle Radiobuttons aktualisieren
     UpdateChip(m);
    }
   }break;

   case 17:
   case 19: {		// Frequenz-Bits
    if (d>=1L<<28) {
     if (dir) d=(long)d<0?0:(1L<<28)-1;	// begrenzen
     if (d==dv) return false;		// unverändert: Piep!
    }
    if (Config.freq[i]!=d) {
     BYTE m=4<<i;
     if ((Config.freq[i]^d)>>14) m|=2;	// High-Teil ändert sich?
     Config.freq[i]=d;
     UpdateValues(i?1L<<18:1L<<16);	// Frequenzangabe aktualisieren
     UpdateChip(m);
    }
   }break;

   case 21:
   case 23: {		// Phasen-Bits
    if (d>=1<<12) {
     if (dir) d&=(1<<12)-1;		// Wrapping ausführen
     else return false;			// nur 12 Bits erlaubt
    }
    if (Config.phase[i]!=d) {
     Config.phase[i]=(WORD)d;
     UpdateValues(i?1L<<22:1L<<20);	// Phasenangabe aktualisieren
     UpdateChip(16<<i);
    }
   }break;
  }
  if (dir) BitsToEdit(d,radix,hMainWnd,id);
 }else{		// gerade IDs sind Gleitkomma-Eingaben
  double f,fv,fs=1,add=dir;
  int nk=3,dp=StringToFloat(s,&f);
  if (!dp) return false;
  if (a || e!=lstrlen(s)) {	// Bei Vollmarkierung bei Einerschritten bleiben
   pot=dp-e;
   if (pot<0) add/=E10(-1-pot);	// Dezimalpunkt als solchen ignorieren
   else add*=E10(pot);
  }
  fv=f;			// Wert vorher
  f+=add;
  switch (id) {
   case 16:
   case 18: {		// Frequenz-Angabe
    if (f<0) {
     if (dir) f=0;			// begrenzen
     if (f==fv) return false;		// unverändert: Piep!
    }
    fs=E10(Config.unitpre);
    f*=fs;
    fv*=fs;
    if (f>=Config.mclk) {
     if (dir) f=Config.mclk-1;		// 1 Hz drunter
     if (f==fv) return false;		// unverändert: Piep!
    }
    d=FromHz(f);
    if (Config.freq[i]!=d) {
     BYTE m=4<<i;
     if ((Config.freq[i]^d)>>14) m|=2;	// High-Teil ändert sich?
     Config.freq[i]=d;
     UpdateValues(i?1L<<19:1L<<17);	// Frequenz-Bits aktualisieren
     UpdateChip(m);
    }
   }break;

   case 20:
   case 22: {		// Phasenwinkel-Angabe
    if (f>+1E5) return false;		// die folgenden Schleifen sinnvoll eingrenzen!
    if (f<-1E5) return false;
    while (f>=360) f-=360;
    while (f<0) f+=360;
    d=(DWORD)(f*(1<<12)/360+0.5);
    d&=(1<<12)-1;			// Überlauf kann (nur) durch Runden passieren
    if (Config.phase[i]!=d) {
     Config.phase[i]=(WORD)d;
     UpdateValues(i?1L<<23:1L<<21);	// Phasen-Bits aktualisieren
     UpdateChip(16<<i);
    }
    nk=2;
   }break;

   case 24: {		// Speisefrequenz-Angabe
    if (f<0) {
     if (dir) f=0;			// begrenzen
     if (f==fv) return false;		// unverändert: Piep!
    }
    fs=E10(6);
    f*=fs; fv*=fs;			// in Hz umrechnen und speichern
    if (f>100E6) {			// nicht mit mehr als 100 MHz (über)takten
     if (dir) f=100E6;			// begrenzen
     if (f==fv) return false;		// unverändert: Piep!
    }
    if (Config.mclk!=f) {
     Config.mclk=(float)f;
     UpdateValues(0x50000L);		// beide Frequenzen neu anzeigen (kein Chip-Update)
    }
    nk=4;
   }break;
  }
  if (dir) FloatToEdit(f/fs,nk,hMainWnd,id);
 }
 return true;
}

static void HandleScroll(HWND Wnd, int dir) {
 BYTE id=(BYTE)GetWindowID(Wnd);
 int sw=10;
 if (id&1 && Config.radix!=10) {	// nicht-dezimale Zahlen: hexadezimale Schrittweite!
  sw=16;
  if (dir==10) dir=sw;
  if (dir==-10) dir=-sw;
 }
 if (GetKeyState(VK_CONTROL)<0) dir*=sw;	// 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;	// kommt nach dem Klicken aufs UpDownControl vorbei: abwürgen!
  }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);
}

BOOL HandleUpDownNotify(HWND Wnd, LPARAM lParam) {
 NMHDR *nm=(NMHDR*)lParam;
 switch (nm->code) {
  case UDN_DELTAPOS: {
   NMUPDOWN *ud=(NMUPDOWN*)nm;
   HWND hEdit=(HWND)SendMessage(nm->hwndFrom,UDM_GETBUDDY,0,0);
   int n=abs(ud->iDelta);
   while (n) {
    int wp=ud->iDelta<0?SB_LINEDOWN:SB_LINEUP;
    if (n>=10) {wp|=SB_PAGEUP; n-=10;} else --n;
    SendMessage(hEdit,WM_VSCROLL,wp,0);
   }
   return SetDlgMsgResult(Wnd,WM_NOTIFY,TRUE);
  }
 }
 return FALSE;
}

static bool TitleExpanded;
static void ExpandTitle(void) {
 if (!TitleExpanded) {
  TCHAR s[64];
  int l=GetWindowText(hMainWnd,s,elemof(s));
  wnsprintf(s+l,elemof(s)-l,T(" #%d"),nInstance);
  SetWindowText(hMainWnd,s);
  TitleExpanded=true;
 }
}

static INT_PTR CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
 switch (Msg) {
  case WM_INITDIALOG: {
   int i;
   hMainWnd=Wnd;
   if (Config.iomode) SetWindowPos(Wnd,0,Config.winpos.x,Config.winpos.y,0,0,SWP_NOSIZE|SWP_NOZORDER);
   for (i=16; i<=24; 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);
   }
   SendMessage(Wnd,WM_WININICHANGE,0,0);
   UpdateValues((UINT)-1);	// alles setzen
   if (nInstance || mInstances) ExpandTitle();
   SetTimer(Wnd,1,200,NULL);	// Initialisierung fortsetzen
  }return TRUE;

  case WM_WININICHANGE: {
   GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SDECIMAL,sDecimal,elemof(sDecimal));
   sDecimal[1]='.';
   sDecimal[2]=',';
   sDecimal[3]=0;
   if (lParam) UpdateValues(0x1550000L);	// alles neu anzeigen, was Dezimaltrenner enthält
  }break;

  case WM_IOERROR: {
   if (MBox(Wnd,10,MB_YESNO|MB_ICONEXCLAMATION)==IDYES) {
    SpiClose();
    if (SpiOpen(Wnd)) break;
   }
   SendMessage(Wnd,WM_COMMAND,127,0);		// Hardwaredialog zeigen
  }break;

  case WM_GETINST: {
   SetWindowLong(Wnd,DWL_MSGRESULT,nInstance);	// diese Nachricht liefert die Instanz-Nummer
   ExpandTitle();				// Wenn jemand fragt, ggf. "#0" dazusetzen (damit man's sieht)
  }return TRUE;

  case WM_COMMAND: {
   if (LOBYTE(wParam)>=128) HandleBitButton(LOBYTE(wParam));
   else switch (LOBYTE(wParam)) {
    case 15:
    case 16:
    case 17:
    case 18:
    case 19:
    case 20:
    case 21:
    case 22:
    case 23:
    case 24: {	// Editfenster
     if (HIWORD(wParam)==EN_CHANGE) SetTimer(Wnd,LOBYTE(wParam),500,NULL);
    }break;
    case 32+0:
    case 32+3:
    case 32+6: {	// Einheiten-Auswahl
     Config.unitpre=LOBYTE(wParam)-32;
     UpdateValues(0x50000L);		// Frequenzen neu anzeigen
    }break;
    case 80+2:
    case 80+10:
    case 80+16: {	// Radix-Auswahl
     Config.radix=LOBYTE(wParam)-80;
     UpdateValues(0xAA0000L);		// FSELx und PSELx neu anzeigen
    }break;
    case 48:
    case 49:
    case 50:
    case 51: {
     static const BYTE cases[]={0,0x28,0x20,0x30};
     WORD c=Config.control&~0x38|cases[wParam-48];	// hier: HIBYTE(wParam)=0
     if (Config.control!=c) {
      Config.control=c;
      UpdateValues(1L<<3|1L<<15);
      UpdateChip(1);
     }
    }break;
    case 102: {
     if (hSweep) SetFocus(hSweep);
     else hSweep=CreateDialog(hInstance,MAKEINTRESOURCE(3),Wnd,SweepDlgProc);
    }break;
    case 127: {
     if (DialogBox(hInstance,MAKEINTRESOURCE(2),Wnd,SetupDlgProc)==IDOK) { // ruft selber SpiClose() und SpiOpen()
      UpdateChip((BYTE)-1);
     }
     ShowConn();	// auch bei IDCANCEL zeigen (jaja, unsauber!)
    }break;
   }
  }break;

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

  case WM_TIMER: {
   KillTimer(Wnd,wParam);			// alles _einmalige_ Timer!
   switch (LOBYTE(wParam)) {
    case 1: {
     if (!Config.iomode) {
      if (!Config.mclk) Config.mclk=50000000L;		// beim ersten Start 50 MHz Oszillatorfrequenz annehmen
      if (!Config.freq[0]) Config.freq[0]=(1L<<28)/50;	// beim ersten Start 1 MHz ausgeben lassen
      UpdateValues(0x01030000);
      SendMessage(Wnd,WM_COMMAND,127,0);
     }else{
      ShowConn();
      if (SpiOpen(Wnd)) UpdateChip((BYTE)-1);
     }
     if (Config.swflag&0x80) {
      SendMessage(Wnd,WM_COMMAND,102,0);	// Sweep-Fenster wieder öffnen
      if (Config.swflag&0x40) {
       PostMessage(hSweep,WM_COMMAND,IDOK,0);	// Sweep starten
      }
     }
     Config.swflag&=~0xC0;
    }break;
    default: if (!HandleEditChange(LOBYTE(wParam),0)) MessageBeep(MB_ICONEXCLAMATION);
   }
  }break;

  case WM_CLOSE: {
   if (!IsIconic(Wnd)) {	// Unsauber: Bei minimiertem Fenster wird keine (restaurierte) Position gespeichert
    RECT R;
    GetWindowRect(Wnd,&R);
    Config.winpos.x=(short)R.left;
    Config.winpos.y=(short)R.top;
   }
   if (hSweep) Config.swflag|=0x80;	// Sweep-Dialog war sichtbar
   if (hMutex) Config.swflag|=0x40;	// Sweep lief noch
   EndDialog(Wnd,0);
  }break;

  case WM_DESTROY: {
   SpiClose();
  }break;

 }
 return FALSE;
}

// Alle (max. 32 möglichen) AD9834-Fenster finden
static BOOL CALLBACK EnumWindowsProc(HWND Wnd,LPARAM lParam) {
 TCHAR cn[32];
 GetClassName(Wnd,cn,elemof(cn));
 if (!lstrcmpi(cn,T("AD9834"))) {
  int i=SendMessage(Wnd,WM_GETINST,0,0);
  mInstances|=1<<i;
 }
 return TRUE;
}

// Gewünschte Instanz herausfinden
// Diese bleibt dann für die gesamte Prozesslebensdauer konstant.
static bool FindInstance(void) {
 int i=0;
 LPTSTR Args=PathGetArgs(GetCommandLine());
 EnumWindows(EnumWindowsProc,0);
 if (!_BitScanForward(&nInstance,~mInstances)) {	// erstes freies Bit
  MBox(0,9,MB_ICONSTOP|MB_OK);
  return false;
 }
 while (*Args==' ') Args++;
 if (*Args) {
  i--;
  if (*Args=='#') i=StrToInt(Args+1);
  if ((unsigned)i>=32) {
   if (MBox(0,8,MB_ICONEXCLAMATION|MB_YESNO,Args,nInstance)!=IDYES) return false;
  }else if (mInstances&1<<i) {
   if (MBox(0,7,MB_ICONQUESTION|MB_YESNO,i,nInstance)!=IDYES) return false;
  }else nInstance=i;
 }
 return true;
}

int WinMainCRTStartup() {
 HINSTANCE inst;
 WNDCLASSEX wc;

 hInstance=inst=GetModuleHandle(NULL);
// GnulInit(inst,NULL,S);
 InitCommonControls();

 InitStruct(&wc,sizeof(wc));
 wc.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
 wc.lpfnWndProc=DefDlgProc;
 wc.cbWndExtra=DLGWINDOWEXTRA;
 wc.hInstance=hInstance;
 wc.hCursor=LoadCursor(0,IDC_ARROW);
 wc.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(1));
 wc.hIconSm=LoadImage(hInstance,MAKEINTRESOURCE(1),IMAGE_ICON,16,16,0);
 wc.lpszClassName=T("AD9834");
 RegisterClassEx(&wc);

 inst=LoadLibrary(LangFileName);
 if (inst) hInstance=inst;
 LoadString(hInstance,1,MBoxTitle,elemof(MBoxTitle));
 if (!FindInstance()) return IDCANCEL;
// Vorgaben laden
 LoadSettings();
 if (Config.radix<2) Config.radix=10;
 DialogBox(hInstance,MAKEINTRESOURCE(1),0,MainDlgProc);
 SaveSettings();
 return IDOK;
}
Detected encoding: UTF-80