Source file: /~heha/messtech/PulseGen.zip/src/PulseGen.c

/* Pulse generator for IGBT test equipment, in conjunction with
 * an Agilent arbitrary waveform generator HP33120A
 * Source tabsize is 8 of course!
 * Matthias Olescher
 080107	BASIC program rewritten as C program (without runtime library)
-080903	small fixes, conditional help button, HTML help, less size
-130404	Bound to msvcrt-light.lib, much easier to handle
	Unlimited & fast COM port enumeration via SetupDI functions
 */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <windowsx.h>	// useful macros
#include <shlwapi.h>
#include <commctrl.h>
#include <uxtheme.h>	// extra question-mark button
#include <tmschema.h>	// even more XP stuff
#include <htmlhelp.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <ntddser.h>


//#define MAXCOMSEARCH 32
#define HELPBUTTON	// enable fourth caption bar button
#define elemof(x) (sizeof(x)/sizeof((x)[0]))
#define T(x) TEXT(x)
#define nobreak
typedef enum {false,true} bool;


static HINSTANCE ghInstance;
static TCHAR MBoxTitle[64];
static TCHAR sDecimal[2];
static TCHAR gIniFileName[MAX_PATH];
static const TCHAR gHelpFileName[]=T("PulseGen.chm");
static HWND ghMainWnd;
static LPCTSTR gDlgCtlAtom;	// for colored dialog controls (via SetProp)
static bool gHelpAvail;
static bool gDirty;

static struct _g {
 int Pulses;	// pulse count, 1..5
 float Volt;	// Voltage Ucc [V]
 float Curr;	// Current Ic [A]
 float Indu;	// Inductance [H]
 float Toff;	// off time (between pulses) [s]
 float Ton[5];	// on times (gTon[0] will be calculated) [s]
 int SerialNo;	// Number of serial port, 1 = COM1 etc.
 int Samples;	// number of samples for waveform
}g;	// No initialization here to create an empty .data section


static const struct _g gDefaults={
 2,			// Pulses
 1800,			// Volt
 1200,			// Curr
 3E-4F,			// Indu
 1E-5F,			// Toff
 {0,1E-5F,1E-5F,1E-5F,1E-5F},	// Ton[5]
 1,			// SerialNo
 4096};			// Samples

// for colored dialog controls:
typedef struct {
// COLORREF ForeColor;
 COLORREF BackColor;
 HBRUSH BackBrush;
}DLGCTLPROP;

static DLGCTLPROP gEditErrorProp; // here: only one color modification

/**********************************
 * general routines from WUTILS.H *
 **********************************/
static void _fastcall InitStruct(LPVOID p, UINT len) {
 *((LPUINT)p)=len; len/=sizeof(UINT); len--;
 if (len) do *++((LPUINT)p)=0; while (--len);
}

// message box with string ressource identifier
static int vMBox(HWND Wnd, UINT id, UINT style, va_list arglist) {
 TCHAR buf1[256],buf2[1024];
 LoadString(ghInstance,id,buf1,elemof(buf1));
 wvnsprintf(buf2,elemof(buf2),buf1,arglist);
 return MessageBox(Wnd,buf2,MBoxTitle,style);
}

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

static void EnableDlgItem(HWND Wnd, UINT id, BOOL state) {
 Wnd=GetDlgItem(Wnd,id);
 if (Wnd) EnableWindow(Wnd,state);
}

static void ShowDlgItem(HWND Wnd, UINT id, int state) {
 Wnd=GetDlgItem(Wnd,id);
 if (Wnd) ShowWindow(Wnd,state);
}

// As SetDlgItemText() but keeping text selection
// and reset the "dirty" bit.
static void SetEditText(HWND Wnd, UINT id, LPTSTR s) {
 DWORD SelStart,SelEnd;
 if (id!=(UINT)-1) Wnd=GetDlgItem(Wnd,id);
 SendMessage(Wnd,EM_GETSEL,(WPARAM)&SelStart,(LPARAM)&SelEnd);
 SetWindowText(Wnd,s);
 Edit_SetSel(Wnd,SelStart,SelEnd);
 Edit_SetModify(Wnd,FALSE);
}

/***************
 * contexthelp *
 ***************/
static void SendWmHelp(HWND Wnd, HWND Child, int MouseX, int MouseY) {
 HELPINFO hi;
 hi.cbSize=sizeof(hi);			// fill-up HELPINFO
 hi.iContextType=HELPINFO_WINDOW;
 hi.iCtrlId=GetDlgCtrlID(Child);
 hi.hItemHandle=Child;
 hi.dwContextId=GetWindowContextHelpId(Child);
 hi.MousePos.x=MouseX;
 hi.MousePos.y=MouseY;
 SendMessage(Wnd,WM_HELP,0,(LPARAM)&hi);
}

// Handler for WM_CONTEXTMENU: creates a pop-up menu
// and then sends WM_HELP
static bool HandleContextMenu(HWND Wnd, HWND Child, LPARAM lParam) {
 HMENU m;
 TCHAR s[64];
 if (Child==Wnd) return false;
 m=CreatePopupMenu();
 LoadString(ghInstance,40/*contexthelp*/,s,elemof(s));
 AppendMenu(m,0,101,s);
 if ((DWORD)lParam==(DWORD)-1) {	// per keyboard
  RECT r;
  GetWindowRect(Child,&r);
  lParam=MAKELONG(r.left,r.bottom);
 }
 if (TrackPopupMenu(m,TPM_LEFTALIGN|TPM_TOPALIGN|TPM_RIGHTBUTTON|TPM_RETURNCMD,
   GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam),0,Wnd,NULL)==101) {
  SendWmHelp(Wnd,Child,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
 }
 DestroyMenu(m);
 return true;
}

#ifdef HELPBUTTON
static void GetQuestionButtonRect(HWND Wnd, LPRECT r) {
 NONCLIENTMETRICS ncm;
 ncm.cbSize=sizeof(ncm);
 SystemParametersInfo(SPI_GETNONCLIENTMETRICS,sizeof(ncm),&ncm,0);
 GetWindowRect(Wnd,r);		// here: measure X extension
 r->left=r->right-r->left
   -GetSystemMetrics(SM_CYFRAME)
   -ncm.iCaptionHeight*4
   +1;
 r->right=r->left+ncm.iCaptionHeight-2;
 r->top=GetSystemMetrics(SM_CXFRAME)+1;	// frame-type dependent
 r->bottom=r->top+ncm.iCaptionHeight-4;
}

// volatile: The optimizer must not remove the <y> funtion parameter!
static BOOL _stdcall NcInQuestionRect(HWND Wnd, int x, volatile int y) {
 RECT r;
 GetWindowRect(Wnd,&r);
 x-=r.left;
 y-=r.top;
 GetQuestionButtonRect(Wnd,&r);
 return PtInRect(&r,*(POINT*)&x);
}

static BOOL _stdcall InQuestionRect(HWND Wnd, int x, int y) {
 ClientToScreen(Wnd,(POINT*)&x);
 return NcInQuestionRect(Wnd,x,y);
}

static void DrawQuestionButton(HWND Wnd, WPARAM wParam/*Region*/, bool Pushed, bool Hot) {
 HDC dc=wParam==1
  ?GetWindowDC(Wnd)
  :GetDCEx(Wnd,(HRGN)wParam,DCX_WINDOW|DCX_INTERSECTRGN);
 RECT r;
 HTHEME th=0;
 HANDLE hLib;
 GetQuestionButtonRect(Wnd,&r);
 hLib=LoadLibraryA("uxtheme");
 if (hLib) {
  th=(HTHEME(_stdcall*)(HWND,LPCWSTR))GetProcAddress(hLib,"OpenThemeData")(Wnd,L"WINDOW");
 }
 if (th) {	// Windows XP "luna" style
  HRESULT (_stdcall*pDrawThemeBackground)(HTHEME,HDC,int,int,const RECT*,const RECT*);
  (FARPROC)pDrawThemeBackground=GetProcAddress(hLib,"DrawThemeBackground");
  pDrawThemeBackground(th,dc,WP_HELPBUTTON,Pushed?HBS_PUSHED:Hot?HBS_HOT:HBS_NORMAL,&r,NULL);
  (HRESULT(_stdcall*)(HTHEME))GetProcAddress(hLib,"CloseThemeData")(th);
 }else{
  DrawFrameControl(dc,&r,DFC_CAPTION,Pushed?DFCS_CAPTIONHELP|DFCS_PUSHED:DFCS_CAPTIONHELP);
 }
 if (hLib) FreeLibrary(hLib);
 ReleaseDC(Wnd,dc);
}

static void QuestionModalLoop(HWND Wnd) {
 MSG Msg;
 bool Pushed=true;
 SetCapture(Wnd);
 DrawQuestionButton(Wnd,1,true,false);

 while (GetMessage(&Msg,0,0,0)) {
  switch (Msg.message) {
   case WM_MOUSEMOVE: {
    bool InQuest=InQuestionRect(Wnd,
      GET_X_LPARAM(Msg.lParam),GET_Y_LPARAM(Msg.lParam))!=0;
    if (Pushed ^ InQuest) {
     Pushed=InQuest;
     DrawQuestionButton(Wnd,1,Pushed,false);
    }
   }break;
   case WM_LBUTTONUP: {
    if (Pushed) {
     PostMessage(Wnd,WM_SYSCOMMAND,SC_CONTEXTHELP,Msg.lParam);
     DrawQuestionButton(Wnd,1,false,false);
    }
    ReleaseCapture();
   }return;			// exit modal loop
  }
  DispatchMessage(&Msg);
 }
}

// Filters various non-client messages to draw and handle question-mark button
// Only for dialogs! This routine is not suitable for normal windows.
// The dialog handler must propagate TRUE return code.
static BOOL HandleDlgNcMessagesForHelp(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
 switch (Msg) {

  case WM_NCPAINT:
  case WM_NCACTIVATE: {
   SetWindowLong(Wnd,DWL_MSGRESULT,DefWindowProc(Wnd,Msg,wParam,lParam));
   DrawQuestionButton(Wnd,1,false,false);
  }return TRUE;

  case WM_NCHITTEST: {
// 1. Suppress dragging window when hitting the question mark
// 2. deliver  wParam==HTHELP at WM_NCLBUTTONDOWN
   if (NcInQuestionRect(Wnd,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)))
     return SetDlgMsgResult(Wnd,WM_NCHITTEST,HTHELP);
  }break;

  case WM_NCMOUSEMOVE: {	// hot tracking (luna style only)
   static bool Hot;
   if (Hot!=(wParam==HTHELP)) {
    Hot=!Hot;
    DrawQuestionButton(Wnd,1,false,Hot);
   }
  }break;

  case WM_NCLBUTTONDOWN:
  case WM_NCLBUTTONDBLCLK: {
   if (wParam==HTHELP) {QuestionModalLoop(Wnd); return TRUE;}	// swallow result
  }break;
  
  case 0xAE:
  case 0xAF: return TRUE;	// unknown XP messages must not reach DefWindowProc
 }
 return FALSE;
}
#endif//HELPBUTTON

/********************************
 * float-point support routines *
 ********************************/
int _fltused;		// against linker error

// Load MSVCRT.DLL and use only the sprintf and sscanf routines
extern int _declspec(dllimport) _cdecl _snwprintf(PWSTR,int,PCWSTR,...);
extern int _declspec(dllimport) _cdecl _snprintf(PSTR,int,PCSTR,...);
extern int _declspec(dllimport) _cdecl swscanf(PCWSTR,PCWSTR,...);
extern int _declspec(dllimport) _cdecl sscanf(PCSTR,PCSTR,...);
#ifdef UNICODE
# define _stprintf _snwprintf
# define _stscanf swscanf
#else
# define _stprintf _snprintf
# define _stscanf sscanf
#endif

// typecast to int: the float value sits on ST(0)
long _declspec(naked) _cdecl _ftol2_sse(void) {
 _asm{	push	eax
 	fistp	dword ptr [esp]
	fwait
	pop	eax
	ret
 }
}

static void Float2String(PTSTR s, int slen, float z) {
 _stprintf(s,slen,T("%.6G"),z);
 s=StrChr(s,T('.'));
 if (s) *s=sDecimal[0];
}

static bool String2Float(PTSTR s, float*z) {
 PTSTR p;
 float f;
 int n;
 p=StrChr(s,sDecimal[0]);
 if (!p) p=StrChr(s,T(','));
 if (p) *p=T('.');
 if (_stscanf(s,T("%g%n"),&f,&n)!=1) return false;
 while (s[n]==' ') n++;		// ignore trailing spaces
 if (s[n]) return false;	// following characters: wrong!
 if (z) *z=f;
 return true;
}

/**********************
 * scrollable numbers *
 **********************/
static void HandleScroll(HWND Wnd, int dir, int min, int max) {
 HWND Parent=GetParent(Wnd);
 UINT id=GetWindowLong(Wnd,GWL_ID);
 BOOL ok;
 int k;
 int i=GetDlgItemInt(Parent,id,&ok,TRUE);
 if (!ok) {
  MessageBeep(MB_ICONEXCLAMATION);
  return;		// no number
 }
 k=i;
 i+=dir;
 if (i>max) i=max;	// limit
 if (i<min) i=min;
 if (i==k) {		// changed?
  MessageBeep(MB_ICONEXCLAMATION);
  return;		// limit reached
 }
 SetDlgItemInt(Parent,id,i,TRUE);	// let EN_CHANGE message do the rest
}

// Instead of using a buddy, scroll bars are equally useful, and requires much less coding!
// Unfortunately, scroll bars must be inserted manually into .RC file (when using MSVC)
// (The same inconenience applies to default edit texts: MSVC is bullshit for resources!)
static WNDPROC DefEditProc;

static LRESULT CALLBACK UpDownEditProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
 switch (Msg) {
  case WM_VSCROLL: switch (LOWORD(wParam)) {
   case SB_LINEUP:
   case SB_PAGEUP:	HandleScroll(Wnd,1,1,5); break;
   case SB_LINEDOWN:
   case SB_PAGEDOWN:	HandleScroll(Wnd,-1,1,5); break;
  }break;
  case WM_KEYDOWN: switch (wParam) {
   case VK_UP:
   case VK_PRIOR:	HandleScroll(Wnd,1,1,5); return 0;	// eat keypress
   case VK_DOWN:
   case VK_NEXT:	HandleScroll(Wnd,-1,1,5); return 0;
  }break;
 }
 return CallWindowProc(DefEditProc,Wnd,Msg,wParam,lParam);
}

/*****************************
 * Calculations and updating *
 *****************************/

// Get the float value out of edit control.
// On erraneous input, set the window property.
static bool GetEditFloat(UINT id, float*val) {
 TCHAR s[32];
 bool ret;
 HWND w=GetDlgItem(ghMainWnd,id);
 if (!Edit_GetModify(w)) return false;
 Edit_SetModify(w,FALSE);
 GetWindowText(w,s,elemof(s));
 ret=String2Float(s,val);
 if (ret) RemoveProp(w,gDlgCtlAtom);
 else SetProp(w,gDlgCtlAtom,(HANDLE)&gEditErrorProp);
 InvalidateRect(w,NULL,TRUE);
 return ret;
}

static BYTE *gWaveform;	// pointer to dynamic allocated array
static float gFullTime;	// time for entire waveform [s]

__forceinline int RoundInt(float f) {
 int i;
 _asm	fld	f
 _asm	fistp	i
 return i;
}

static void SetPulse(float from, float to) {
 int a=RoundInt(from/gFullTime*g.Samples);
 int e=RoundInt(to/gFullTime*g.Samples);
 if (e>g.Samples) e=g.Samples;
 if (e>a) memset(gWaveform+a,TRUE,e-a);
}

// As name implies, this routine recalculates the (binary) waveform
// out of timing parameters and desired sample count.
static void CalcWaveform() {
 int i;
 float t;
 gFullTime=g.Ton[0];
 if (gWaveform) LocalFree(gWaveform);
 gWaveform=LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT,g.Samples);
 for (i=1; i<g.Pulses; i++) {
  gFullTime+=g.Toff+g.Ton[i];
 }
 for (t=0, i=0; i<g.Pulses; i++) {
  SetPulse(t,t+g.Ton[i]);
  t+=g.Toff+g.Ton[i];
 }
 InvalidateRect(GetDlgItem(ghMainWnd,12),NULL,TRUE);	// draw curve
 gDirty=true;
}

// Don't optimize parameter passing here!
static BOOL _stdcall Line(HDC dc, int x1, int y1, int x2, int y2) {
 int a=y1+x2+y2;
 return Polyline(dc,(POINT*)&x1,2)+a;
}

static void DrawCurve(PDRAWITEMSTRUCT dis) {
 POINT *pt=LocalAlloc(LMEM_FIXED,(g.Samples+4)*sizeof(POINT));
 POINT *ptp;
 RECT r;
 HBRUSH br;
 HPEN pen;
 int x,y;
 CopyRect(&r,&dis->rcItem);
 br=CreateSolidBrush(RGB(255,204,153));
 SelectBrush(dis->hDC,br);
 SelectPen(dis->hDC,GetStockPen(BLACK_PEN));
 Rectangle(dis->hDC,r.left,r.top,r.right,r.bottom);
 DeleteBrush(br);
 InflateRect(&r,-15,-10);	// area for curve
 pen=CreatePen(PS_SOLID,0,RGB(128,128,128));
 SelectPen(dis->hDC,pen);
 Line(dis->hDC,dis->rcItem.left+1,r.top,dis->rcItem.right-1,r.top);
 Line(dis->hDC,dis->rcItem.left+1,r.bottom,dis->rcItem.right-1,r.bottom);
 Line(dis->hDC,r.left,dis->rcItem.top+1,r.left,dis->rcItem.bottom-1);
 Line(dis->hDC,r.right,dis->rcItem.top+1,r.right,dis->rcItem.bottom-1);
 DeletePen(pen);
 pen=CreatePen(PS_DOT,0,RGB(0,0,0));
 SelectPen(dis->hDC,pen);
 y=(r.top+r.bottom)/2;		// middle
 Line(dis->hDC,dis->rcItem.left+1,y,dis->rcItem.right-1,y);
 DeletePen(pen);
 pen=CreatePen(PS_SOLID,2,RGB(0,0,0));
 SelectPen(dis->hDC,pen);
 if (!pt) return;
 ptp=pt;
 ptp->x=r.left-15;	// show leading LOW level
 ptp->y=r.bottom;
 ptp++;
 ptp->x=r.left;
 ptp->y=r.bottom;
 ptp++;
 if (gWaveform) for (x=0; x<g.Samples; x++,ptp++) {
  ptp->x=MulDiv(x+1,r.right-r.left-1,g.Samples+1)+r.left;
  ptp->y=gWaveform[x]?r.top:r.bottom;
 }
 ptp->x=r.right;	// show trailing LOW level
 ptp->y=r.bottom;
 ptp++;
 ptp->x=r.right+15;
 ptp->y=r.bottom;
 Polyline(dis->hDC,pt,g.Samples+4);
 DeletePen(pen);
 LocalFree(pt);
}

static void PulsesChanged() {
 int i;
 HWND w=GetDlgItem(ghMainWnd,18);	// don't show Toff in case of only one pulse
 BOOL b=g.Pulses>1;
 ShowWindow(GetPrevSibling(w),b);	// Label (Toff)
 ShowWindow(w,b);			// Edit control
 ShowWindow(GetNextSibling(w),b);	// Unit (µs)
 for (i=2; i<=5; i++) {
  w=GetDlgItem(ghMainWnd,20+i-2);
  b=i<=g.Pulses;
  ShowWindow(GetPrevSibling(w),b);	// Label (TonX)
  ShowWindow(w,b);			// Edit control
  ShowWindow(GetNextSibling(w),b);	// Unit (µs)
 }
 CalcWaveform();
}

static void CalcTon0(void) {
 if (g.Volt) {
  TCHAR buf[32];
  g.Ton[0]=g.Indu*g.Curr/g.Volt;
  Float2String(buf,elemof(buf),g.Ton[0]*1E6F); SetEditText(ghMainWnd,19,buf);	// show micro-seconds
  CalcWaveform();
 }
}

static BOOL GetString(PTSTR key, PTSTR value, int vlen) {
 return GetPrivateProfileString(T("PulseGen"),key,T(""),value,vlen,gIniFileName);
}

static BOOL PutString(PTSTR key, PTSTR value) {
 return WritePrivateProfileString(T("PulseGen"),key,value,gIniFileName);
}

static void LoadConfig(void) {
 TCHAR buf[32];
 int i;
 g.Pulses=GetPrivateProfileInt(T("PulseGen"),T("Pulses"),g.Pulses,gIniFileName);
 SetDlgItemInt(ghMainWnd,14,g.Pulses,FALSE);
 PulsesChanged();
 GetString(T("Voltage(V)"),buf,elemof(buf));   String2Float(buf,&g.Volt);
 GetString(T("Current(A)"),buf,elemof(buf));   String2Float(buf,&g.Curr);
 GetString(T("Inductance(H)"),buf,elemof(buf));String2Float(buf,&g.Indu);
 GetString(T("TimeOff(s)"),buf,elemof(buf));   String2Float(buf,&g.Toff);
 Float2String(buf,elemof(buf),g.Volt);      SetEditText(ghMainWnd,15,buf);
 Float2String(buf,elemof(buf),g.Curr);      SetEditText(ghMainWnd,16,buf);
 Float2String(buf,elemof(buf),g.Indu*1E6F); SetEditText(ghMainWnd,17,buf);	// show micro-henry
 Float2String(buf,elemof(buf),g.Toff*1E6F); SetEditText(ghMainWnd,18,buf);	// show micro-seconds
 for (i=1; i<elemof(g.Ton); i++) {
  TCHAR key[32];
  wnsprintf(key,elemof(key),T("TimeOn%d(s)"),i+1);
  GetString(key,buf,elemof(buf)); String2Float(buf,g.Ton+i);
  Float2String(buf,elemof(buf),g.Ton[i]*1E6F); SetEditText(ghMainWnd,19+i,buf); // show micro-seconds
 }
 g.SerialNo=GetPrivateProfileInt(T("PulseGen"),T("COM"),g.SerialNo,gIniFileName);
 g.Samples=GetPrivateProfileInt(T("PulseGen"),T("Samples"),g.Samples,gIniFileName);
 SetDlgItemInt(ghMainWnd,32,g.Samples,FALSE);
 CalcTon0();		// calls CalcWaveform() too
}

static void SaveConfig(void) {
 TCHAR buf[32];
 int i;
 wnsprintf(buf,elemof(buf),T("%d"),g.Pulses); PutString(T("Pulses"),buf);
 _stprintf(buf,elemof(buf),T("%.6G"),g.Volt); PutString(T("Voltage(V)"),buf);	
 _stprintf(buf,elemof(buf),T("%.6G"),g.Curr); PutString(T("Current(A)"),buf);	
 _stprintf(buf,elemof(buf),T("%.6G"),g.Indu); PutString(T("Inductance(H)"),buf);	
 _stprintf(buf,elemof(buf),T("%.6G"),g.Toff); PutString(T("TimeOff(s)"),buf);	
 for (i=1; i<elemof(g.Ton); i++) {
  TCHAR key[32];
  wnsprintf(key,elemof(key),T("TimeOn%d(s)"),i+1);
  _stprintf(buf,elemof(buf),T("%.6G"),g.Ton[i]); PutString(key,buf);	
 }
 wnsprintf(buf,elemof(buf),T("%d"),g.SerialNo); PutString(T("COM"),buf);
 wnsprintf(buf,elemof(buf),T("%d"),g.Samples); PutString(T("Samples"),buf);
}

/************************
 * serial communication *
 ************************/
static HANDLE ghCom;
static OVERLAPPED gOvl;

// This routine may process messages!
static BOOL OpenSerial(void) {
 TCHAR ComName[12];
 DCB dcb;
 static const COMMTIMEOUTS to={0,0,100,2,1000};

 wnsprintf(ComName,elemof(ComName),T("\\\\.\\COM%u"),g.SerialNo);
 ghCom=CreateFile(ComName,GENERIC_READ|GENERIC_WRITE,
     0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
 if (ghCom==INVALID_HANDLE_VALUE) {
  MBox(ghMainWnd,20,MB_OK,ComName);
  return FALSE;
 }
 gOvl.hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
 InitStruct(&dcb,sizeof(dcb));
 BuildCommDCB(T("9600,n,8,2"),&dcb);
 dcb.fOutxDsrFlow=TRUE;
 dcb.fDtrControl=DTR_CONTROL_HANDSHAKE;	// full handshake (required by HP33120A for 9600 baud)
 dcb.fRtsControl=RTS_CONTROL_ENABLE;
 return SetCommState(ghCom,&dcb) && SetCommTimeouts(ghCom,(LPCOMMTIMEOUTS)&to);
}

static void CloseSerial(bool DoFlush) {
 if (DoFlush) FlushFileBuffers(ghCom);
 CloseHandle(ghCom);
 CloseHandle(gOvl.hEvent);
}

// Waiting for gOvl.hEvent
static void WaitOvl(void) {
 while (MsgWaitForMultipleObjects(1,&gOvl.hEvent,TRUE,1000,QS_ALLINPUT)==1) {
  MSG Msg;
  while (PeekMessage(&Msg,0,0,0,PM_REMOVE)) {
   if (Msg.message==WM_QUIT) return;	// break loop in WM_QUIT
   if (Msg.message!=WM_COMMAND) {	// don't process WM_COMMAND
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
   }
  }
 }
}

static int SendSerial(LPSTR data,int len) {
 if (len==-1) len=lstrlenA(data);
 WriteFile(ghCom,data,len,&len,&gOvl);
 WaitOvl();
 return len;
}

static int RecvSerial(LPSTR data, int len) {
 ReadFile(ghCom,data,len,&len,&gOvl);
 WaitOvl();
 return len;
}

// prepare and transmit waveform in one chunk (easier to debug)
static void TransmitWaveform(void) {
 char *buf;
 PWORD p;
 int i;
 buf=LocalAlloc(LMEM_FIXED,30+g.Samples*2);
 i=wnsprintfA(buf,30,"%u",g.Samples*2);	// number of characters (buf unused)
 i=wnsprintfA(buf,30,"DATA:DAC VOLATILE,#%u%u",i,(g.Samples+2)*2);
 p=(PWORD)(buf+i);
 *p++=0;			// first sample must be 0 (otherwise 5V will be steady before sending first wavefoem)
 for (i=0; i<g.Samples; i++) {
  *p++ = gWaveform[i] ? 2047 : 0;	// append binary data
 }
 *p++=0;			// last sample must be 0 (otherwise 5V will be steady after sending first waveform)
 *(char*)p='\n';
 SendSerial(buf,(char*)p-buf+1);
 LocalFree(buf);
}

static void Unremote(void) {
 if (!OpenSerial()) return;
 SendSerial("SYST:LOC\n",-1);		// enable device's pushbuttons
 Sleep(100);
 CloseSerial(false);
}

// This routine may process messages!
static void SendWaveform(void) {
 CHAR buf[64];
 if (!OpenSerial()) return;
 SendSerial("*RST\nSYST:REM\n",-1);		// enable remote control, disable local pushbuttons
 RecvSerial(buf,sizeof(buf));			// clear answer buffer
 SendSerial("*IDN?\n",-1);			// should return "HEWLETT-PACKARD,33120A,..."
 RecvSerial(buf,sizeof(buf));
 buf[23]=0;
 if (lstrcmp(buf,"HEWLETT-PACKARD,33120A,")) {
  CloseSerial(false);
  MBox(ghMainWnd,21,MB_OK);
  return;
 }
 SendSerial(
   "DISP:TEXT \"Loading\"\n"
   "TRIG:SOUR BUS\n"		// trigger source: BUS (i.e. this serial port)
   "BM:STAT ON\n"
   "FORM:BORD SWAP\n",-1);	// binary data in "Intel" byte order
 _stprintf(buf,elemof(buf),"FREQ %f\n\n",1/gFullTime*(g.Samples+2)/g.Samples);
 SendSerial(buf,-1);
 TransmitWaveform();
 SendSerial(
   "DISP:TEXT:CLE\n"
   "FUNC:USER VOLATILE\n"
   "FUNC:SHAP USER\n"
   //"APPL:USER %f,5,0\n"	// set waveform, frequency, voltage, and offset using one instruction!
   "OUTP:LOAD 50\n"		// output resistance: 50 Ohm
   "VOLT 5\n"			// output voltage: 5 V (for maximum DAC value)
   "BM:NCYC 1\n",-1);		// number of cycles per trigger: 1
 CloseSerial(true);
 gDirty=false;
}

// This routine may process messages!
static void StartPulse(void) {
 if (!OpenSerial()) return;
 SendSerial("*TRG\n",-1);
 CloseSerial(true);
}

/*************************
 * main window procedure *
 *************************/
static RECT gMaxRect;

static BOOL CALLBACK MainDlgProc(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam) {
#ifdef HELPBUTTON
 if (gHelpAvail && IsZoomed(Wnd) && HandleDlgNcMessagesForHelp(Wnd,Msg,wParam,lParam)) return TRUE;
#endif
 switch (Msg) {
  case WM_INITDIALOG: {
   HDC dc;
   WINDOWPLACEMENT wp;
   RECT r;
   ghMainWnd=Wnd;
   GetWindowText(Wnd,MBoxTitle,elemof(MBoxTitle));
   DefEditProc=SubclassWindow(GetDlgItem(Wnd,14),UpDownEditProc);
   dc=GetDC(Wnd);
   gEditErrorProp.BackColor=GetNearestColor(dc,RGB(255,128,128));	// light red
   gEditErrorProp.BackBrush=CreateSolidBrush(gEditErrorProp.BackColor);
   ReleaseDC(Wnd,dc);
   GetWindowRect(Wnd,&gMaxRect);
   GetWindowRect(GetDlgItem(Wnd,11),&r);	// <<</>>> button
   wp.length=sizeof(wp);
   GetWindowPlacement(Wnd,&wp);
   wp.ptMaxPosition.x=gMaxRect.left;
   wp.ptMaxPosition.y=gMaxRect.top;
   wp.rcNormalPosition.right=r.right+5;
   wp.rcNormalPosition.bottom=r.bottom+5;
   wp.showCmd=SW_SHOWMAXIMIZED;
   SetWindowPlacement(Wnd,&wp);
   SetTimer(Wnd,1,10,NULL);	// do further initializations later
  }return TRUE;

  case WM_TIMER: {
   KillTimer(Wnd,wParam);	// all timers are non-periodic
   switch (wParam) {
    case 1: {		// continued initialization
     SendMessage(Wnd,WM_SETTINGCHANGE,0,0);
     LoadConfig();
     SendMessage(Wnd,WM_DEVICECHANGE,0,0);
    }break;

    case 15: {		// deferred voltage edit change
     float f;
     if (GetEditFloat(wParam,&f) && 1<=f && f<=1E5) {
      g.Volt=f;
      CalcTon0();
     }
    }break;

    case 16: {		// deferred current edit change
     float f;
     if (GetEditFloat(wParam,&f) && 0.1<=f && f<=1E5) {
      g.Curr=f;
      CalcTon0();
     }
    }break;

    case 17: {		// deferred inductance edit change
     float f;
     if (GetEditFloat(wParam,&f) && 1<=f && f<=1E5) {
      g.Indu=f*1E-6F;
      CalcTon0();
     }
    }break;

    case 18: {		// deferred Toff edit change
     float f;
     if (GetEditFloat(wParam,&f) && 0.5<=f && f<=1E5) {
      g.Toff=f*1E-6F;
      CalcWaveform();
     }
    }break;

    case 20:
    case 21:
    case 22:
    case 23: {		// deferred Ton edit changes
     float f;
     if (GetEditFloat(wParam,&f) && 0.5<=f && f<=1E5) {
      g.Ton[wParam-19]=f*1E-6F;
      CalcWaveform();
     }
    }
   }
  }break;

  case WM_GETMINMAXINFO: {
   PMINMAXINFO pmmi=(PMINMAXINFO)lParam;
   pmmi->ptMaxSize.x=gMaxRect.right-gMaxRect.left;
   pmmi->ptMaxSize.y=gMaxRect.bottom-gMaxRect.top;
  }break;

  case WM_DEVICECHANGE: {
   HWND hCombo;
   int i;
// re-read available serial ports
   hCombo=GetDlgItem(Wnd,30);
   ComboBox_ResetContent(hCombo);
#if 0
   {HMODULE hLib;
    hLib=LoadLibraryA("cfgmgr32.dll");	// speed-up W2K, XP
    for (i=1; i<MAXCOMSEARCH; i++) {
     DWORD cclen;
     TCHAR ComName[8];
     COMMCONFIG cc;
     InitStruct(&cc,cclen=sizeof(cc));
     wnsprintf(ComName,elemof(ComName),T("COM%u"),i);
     if (GetDefaultCommConfig(ComName,&cc,&cclen)) {
      int idx=ComboBox_AddString(hCombo,ComName);
      ComboBox_SetItemData(hCombo,idx,i);
      if (i==g.SerialNo) ComboBox_SetCurSel(hCombo,idx);
     }
    }
    if (hLib) FreeLibrary(hLib);
   }
#else
   {HDEVINFO devs;
    devs=SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT,0,0,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
    if (devs!=INVALID_HANDLE_VALUE) {
//     SP_DEVICE_INTERFACE_DATA devinterface;
     SP_DEVINFO_DATA devinfo;
//     devinterface.cbSize=sizeof devinterface;
     devinfo.cbSize=sizeof devinfo;
     for (i=0; SetupDiEnumDeviceInfo(devs,i,&devinfo); i++) {
      TCHAR s[16];
      DWORD size=sizeof s;
      int k;
      HKEY hKey;
      if (CM_Open_DevNode_Key(devinfo.DevInst,KEY_QUERY_VALUE,0,RegDisposition_OpenExisting,&hKey,CM_REGISTRY_HARDWARE)) continue;
      if (RegQueryValueEx(hKey,T("PortName"),NULL,NULL,(LPBYTE)s,&size)
      || RegCloseKey(hKey)) continue;
      if (_stscanf(s,T("COM%d"),&k)==1) {
       int idx=ComboBox_AddString(hCombo,s);
       ComboBox_SetItemData(hCombo,idx,k);
       if (k==g.SerialNo) ComboBox_SetCurSel(hCombo,i);
      }
     }
     SetupDiDestroyDeviceInfoList(devs);
    }
   }
#endif
  }break;

  case WM_WININICHANGE: {	// Wenn in Systemsteuerung Punkt/Komma wechselt
   GetProfileString(T("intl"),T("sDecimal"),T("."),sDecimal,elemof(sDecimal));
  }break;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 10: {	// "Go" button - start pulse output
    if (gDirty) switch (MBox(Wnd,22,MB_YESNOCANCEL)) {
     case IDYES: SendMessage(Wnd,WM_COMMAND,13,(LPARAM)GetDlgItem(Wnd,13)); break;
     case IDCANCEL: goto raus;
    }
    StartPulse();
raus:;
   }break;

   case 11: {	// normal/maximize button
    ShowWindow(Wnd,IsZoomed(Wnd)?SW_RESTORE:SW_MAXIMIZE);
   }break;

   case 13: {
    EnableWindow((HWND)lParam,FALSE);	// show activity
    SetCursor(LoadCursor(0,IDC_WAIT));
    SendWaveform();
    EnableWindow((HWND)lParam,TRUE);
   }break;

   case 14: switch (HIWORD(wParam)) {
    case EN_CHANGE: {
     int i=GetDlgItemInt(Wnd,14,NULL,FALSE);
     if (0<i && i<=5) {
      RemoveProp((HWND)lParam,gDlgCtlAtom);
      g.Pulses=i;
      PulsesChanged();
     }else SetProp((HWND)lParam,gDlgCtlAtom,&gEditErrorProp);
     InvalidateRect((HWND)lParam,NULL,TRUE);
    }break;
   }break;

   case 15:	// Voltage Edit
   case 16:	// Current Edit
   case 17:	// Inductance Edit
   case 18:	// Toff Edit
   case 20:	// Ton2 Edit
   case 21:	// Ton3 Edit
   case 22:	// Ton4 Edit
   case 23:	// Ton5 Edit
   switch (HIWORD(wParam)) {
    case EN_CHANGE: {
// on change, defer updating of internal variables
     SetTimer(Wnd,LOWORD(wParam),200,NULL); 
    }break;
   }break;

   case 30: switch (HIWORD(wParam)) {
    case CBN_SELCHANGE: {
     g.SerialNo=ComboBox_GetItemData((HWND)lParam,ComboBox_GetCurSel((HWND)lParam));
    }break;
   }break;

   case 32: switch (HIWORD(wParam)) {
    case EN_CHANGE: {
     int i=GetDlgItemInt(Wnd,32,NULL,FALSE);
     if (8<=i && i<=8190) {	// limits given by HP33120A;
// for more samples, the maximum frequency greatly reduces
// The HP33120A documentation does not explain output behaviour
// for small sample counts, i.e. whether linear approximation is done,
// or the output holds the sample the entire sampling time.
// In the latter case, the graphic does not show real waveform.
      RemoveProp((HWND)lParam,gDlgCtlAtom);
      g.Samples=i;
      CalcWaveform();
     }else SetProp((HWND)lParam,gDlgCtlAtom,&gEditErrorProp);
     InvalidateRect((HWND)lParam,NULL,TRUE);
    }break;
   }break;
  }break;

  case WM_CTLCOLOREDIT: {
   DLGCTLPROP *pep=GetProp((HWND)lParam,gDlgCtlAtom);
   if (!pep) break;	// do nothing if no property is set
   SetBkMode((HDC)wParam,TRANSPARENT);
   SetBkColor((HDC)wParam,pep->BackColor);
// SetTextColor((HDC)wParam,pep->Forecolor);
   return (BOOL)pep->BackBrush;
  }

  case WM_HELP: if (gHelpAvail) {
   LPHELPINFO hi=(LPHELPINFO)lParam;
   HtmlHelp(hi->hItemHandle,gHelpFileName,HH_HELP_CONTEXT,hi->iCtrlId);
  }break;
  
  case WM_CONTEXTMENU: if (HandleContextMenu(Wnd,(HWND)wParam,lParam)) return TRUE; break;

  case WM_SIZE: {
   SetDlgItemTextA(Wnd,11,wParam==SIZE_MAXIMIZED?"<&<<":">&>>");
  }break;

  case WM_DRAWITEM: DrawCurve((PDRAWITEMSTRUCT)lParam); break;
  
  case WM_ENDSESSION: if (wParam) {
   SaveConfig();
   if (!gDirty) Unremote();
   DeleteBrush(gEditErrorProp.BackBrush);
  }break;

  case WM_SYSCOMMAND: switch (wParam&0xFFF0) {
   case SC_CLOSE: {
    SendMessage(Wnd,WM_ENDSESSION,TRUE,0);
    EndDialog(Wnd,0);
   }break;
  }break;

 }
 return FALSE;
}

/******************************
 * main program (entry point) *
 ******************************/
void WinMainCRTStartup(void) {
 HANDLE f;
 UINT ret;
 ghInstance=GetModuleHandle(NULL);
 InitCommonControls();
 GetModuleFileName(NULL,gIniFileName,elemof(gIniFileName));
 lstrcpy(PathFindExtension(gIniFileName),T(".ini"));
 gDlgCtlAtom=(LPCTSTR)GlobalAddAtomA("h#s?Color");
 g=gDefaults;
 f=CreateFile(gHelpFileName,0,FILE_SHARE_READ|FILE_SHARE_WRITE,
   NULL,OPEN_EXISTING,0,0);
 if (f!=INVALID_HANDLE_VALUE) {
  CloseHandle(f);
  gHelpAvail=true;
 }
 ret=DialogBox(0,MAKEINTRESOURCE(100),0,MainDlgProc);
 GlobalDeleteAtom((ATOM)gDlgCtlAtom);
 ExitProcess(ret);
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded