Source file: /~heha/mb-iwp/Antriebe/Schrittmotorsteuerung/smc.zip/smc2.cpp

#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <setupapi.h>
#include <devguid.h>

#include "sm.h"
#include "TabDlg.h"

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

#define elemof(x) (sizeof(x)/sizeof((x)[0]))
#define T(x) TEXT(x)
#define nobreak
#define offsetof(T,x) (int)&(((T*)NULL)->x)

EXTERN_C int _fltused;
int _fltused;

__forceinline __int64 rndint64(double f) {
 __int64 i;
 _asm	fld	f
 _asm	fistp	i
 return i;
}

HINSTANCE ghInstance;
TCHAR StdMBoxTitle[64];
char sDecimal[4];	// je nach Windows-Einstellung
char sNegativeSign[4];

struct config_t{
 POINTS winpos;		// Linke obere Ecke beim Speichern (nur für Hauptfenster)
 BYTE ViewMode;		// 0..2 (mit Einheit, Ganzschritte, native), Bit 2 = hex
 BYTE CurTab;		// Aktueller "Reiter"
 BYTE ComNum;		// 0 = COM1 usw. (ungenutzt bei Sekundärfenstern!!)
 BYTE MotorNum;		// 0..5 (Hauptfenster)
 move_t soll;
 void LoadConfig(HWND);
 void SaveConfig(HWND);
};

struct CDLG {		// Dialog-Daten
 config_t c;		// Was in die Registry geht
 struct :DLGHDR{	// Verwaltung der Tabs
  DLGPAGE room[2];
 }p;
 union{
  char nk[4];		// Ausgerechnete Nachkommastellen für den aktuellen Motor
  DWORD nka;
 };
 ram_t ram;
 BYTE RamWriteOffset;
 void IncRamWriteOffsetTo(BYTE newval);
 void Page0Change(BYTE m=0xFF);
 void Page1Change(BYTE m=0xFF);
 void SetValue(HWND,long v,int e);
 void SetValue(HWND w, UINT id, long v, int e) {SetValue(GetDlgItem(w,id),v,e);}
 void SetJerk (HWND w, UINT id, long v) {SetValue(w,id,v,3);}
 void SetAccel(HWND w, UINT id, long v) {SetValue(w,id,v,2);}
 void SetSpeed(HWND w, UINT id, long v) {SetValue(w,id,v,1);}
 void SetPos  (HWND w, UINT id, long v) {SetValue(w,id,v,0);}
 void SetUnit (HWND,int e);
 void SetUnit (HWND w, UINT id, int e) {SetUnit(GetDlgItem(w,id),e);}
 bool GetValue(HWND w, long*p, int e=0);
 bool GetJerk (HWND w,BYTE &p) {return GetValue(w,(long*)&p,3);}
 bool GetAccel(HWND w,char &p) {return GetValue(w,(long*)&p,2);}
 bool GetSpeed(HWND w,short&p) {return GetValue(w,(long*)&p,1);}
 bool GetPos  (HWND w,long &p) {return GetValue(w,&p);}
 void OnMotorChange();
 void PeriodicAction();
 void ViewModeChanged() {Page0Change(4);Page1Change(4);}
 bool Fahrt(bool);
};

CDLG main;
// Für ComboBoxEx (serielle Schnittstellen)
SP_CLASSIMAGELIST_DATA SetupDiClassImageList;

#define ghMainWnd main.p.hwndDlg


//Win32-typische Strukturen mit DWORD-Ausrichtung initialisieren
void _fastcall InitStruct(LPVOID p, UINT len) {
 LPUINT p2=(LPUINT)p;		// sonst hat C++ Verdauungsstörungen
 *p2=len; len/=sizeof(UINT); len--;
 if (len) do *++p2=0; while (--len);
}

int vMBox(HWND Wnd, LPCTSTR Text, UINT Type, va_list va) {
 TCHAR buf[256],buf2[256];
 if (!((DWORD_PTR)Text>>16)) {
  LoadString(ghInstance,(UINT)(DWORD_PTR)Text,buf2,elemof(buf2));
  Text=buf2;
 }
 _vsntprintf(buf,elemof(buf),Text,va);
 return MessageBox(Wnd,buf,StdMBoxTitle,Type);
}

int _cdecl MBox(HWND Wnd, LPCTSTR Text, UINT Type, ...) {
 return vMBox(Wnd,Text,Type,(va_list)(&Type+1));
}

void SetCheckboxGroup(HWND Wnd, UINT u, UINT o, UINT v) {
 for(;u<=o; u++,v>>=1) CheckDlgButton(Wnd,u,v&1);
}

// Punkt->Komma
void pk(char*s) {
 char*p=strpbrk(s,sDecimal);
 if (p) *p=sDecimal[0];
 p=strpbrk(s,sNegativeSign);
 if (p) *p=sNegativeSign[0];
}

char Inf[3][8];	// Infinite-Strings

const char* infstr(int v) {
 unsigned u=v-0x7FFFFFFE;
 if (u>=3) return NULL;
 return Inf[u];
}

EXTERN_C _declspec(naked) _cdecl _alloca_probe() {_asm{
	pop	ecx
	sub	esp,eax
	jmp	ecx
}}

int fromUTF8(char*s, int sl) {
 WCHAR *buf=(WCHAR*)_alloca(sl<<1);
 int l=MultiByteToWideChar(CP_UTF8,0,s,-1,buf,sl);
#ifdef UNICODE
 memcpy(s,buf,l<<1);
#else
 WideCharToMultiByte(CP_ACP,0,buf,l,s,sl,NULL,NULL);
#endif
 return sl;
}

int toUTF8(char*s, int sl) {
#ifdef UNICODE
 int l=wcslen((PWSTR)s)+1;	// String-Länge in Zeichen, inklusive \0
 WCHAR *buf=(WCHAR*)_alloca(l<<1);
 memcpy(buf,s,l<<1);
#else
 WCHAR *buf=(WCHAR*)_alloca(sl<<1);
 int l=MultiByteToWideChar(CP_ACP,0,s,-1,buf,sl);
#endif
 return WideCharToMultiByte(CP_UTF8,0,buf,l,s,sl,NULL,NULL);
}

static HANDLE ComOpen(int n) {
 TCHAR ComName[12];
 _sntprintf(ComName,elemof(ComName),T("\\\\.\\COM%u"),n+1);
 HANDLE h=CreateFile(ComName,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
 if ((int)h<0) h=0;
 if (h) {
  DCB dcb;
  InitStruct(&dcb,sizeof dcb);
  dcb.BaudRate=38400,
  dcb.fBinary=TRUE;
  dcb.ByteSize=8;	// Alles andere bleibt 0 (NOPARITY,ONESTOPBIT,NOHANDSHAKE)
  SetCommState(h,&dcb);
  static const COMMTIMEOUTS to={0,0,100,0,0};
  SetCommTimeouts(h,(LPCOMMTIMEOUTS)&to);
 }
 return h;
}

static int trafo(int v, int u, int o, int U, int O) {
 return MulDiv(v-u,O-U,o-u)+U;
}

static double trafo(int v, int u, int o, double U, double O) {
 return (v-u)*(O-U)/(o-u)+U;
}

/***************************
 * Noch mehr globale Daten *
 ***************************/

HANDLE hCom;		// Aktive serielle Schnittstelle
OVERLAPPED o;
union{
 struct{
  EeCtrl h;		// 8 Byte Header
  EeMotor m[6];		// 48 Byte Motordaten (zumeist 6mal)
 };
 BYTE data[512];	// Speicherabzug des gesamten EEPROMs
}Eeprom;

int Query(const void *q, int ql, void *a, int al) {
 DWORD dw;
 WriteFile(hCom,q,ql,&dw,&o);
 GetOverlappedResult(hCom,&o,&dw,TRUE);
 ReadFile(hCom,a,al,&dw,&o);
 GetOverlappedResult(hCom,&o,&dw,TRUE);
 return dw;
}

void QueryBasicParams() {
 char s[100];
 memset(s,0,sizeof(s));
 s[99]='?';
 s[Query(s,100,s,99)]=0;
 fromUTF8(s,100);
 SetDlgItemText(ghMainWnd,18,(PTSTR)s);
// EEPROM (komplett) auslesen
 read_t q={0xA0,4,0,0x81,0x80};
 for(;q.a<512;q.a+=q.l) {
  Query(&q,sizeof q,Eeprom.data+q.a,q.l);
 }
 SendDlgItemMessage(ghMainWnd,22,UDM_SETRANGE32,0,(Eeprom.h.nm&0x0F)-1);
}

void SerComboUpdate(HWND hCombo) {
 COMBOBOXEXITEM cbei;
 cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_OVERLAY|CBEIF_LPARAM;
 cbei.iItem=0;
 ComboBox_ResetContent(hCombo);
 HANDLE devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,NULL,0,DIGCF_PRESENT);
 if (devs!=INVALID_HANDLE_VALUE) {
  SP_DEVINFO_DATA devInfo;
  devInfo.cbSize=sizeof devInfo;
  for (DWORD i=0; SetupDiEnumDeviceInfo(devs,i,&devInfo); i++) {
   HKEY hKey;
   TCHAR s[64];
   DWORD slen=sizeof s;
   DWORD num;
   if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL,0,DIREG_DEV,KEY_READ))
     ==INVALID_HANDLE_VALUE) continue;
   if (!RegQueryValueEx(hKey,T("PortName"),NULL,NULL,(LPBYTE)s,&slen)
   && _stscanf(s,T("COM%u"),&num)==1) {	// LPTx ausfiltern
    cbei.lParam=--num;
    SetupDiGetDeviceRegistryProperty(devs,&devInfo,SPDRP_ENUMERATOR_NAME,NULL,(PBYTE)s,sizeof s,NULL);
    SetupDiGetClassImageIndex(&SetupDiClassImageList,(LPGUID)
      _tcsicmp(s,T("usb"))?&GUID_DEVCLASS_PORTS:&GUID_DEVCLASS_USB,&cbei.iImage);
    cbei.iSelectedImage=cbei.iImage;
    SetupDiGetDeviceRegistryProperty(devs,&devInfo,SPDRP_FRIENDLYNAME,NULL,(PBYTE)s,sizeof s,NULL);
    cbei.pszText=s;
    cbei.iOverlay=0;
    if (num!=main.c.ComNum) {
     HANDLE h=ComOpen(num);
     if (h) CloseHandle(h); else cbei.iOverlay=2;	// rotes X: Port besetzt
    }
    SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei);
    if (num==main.c.ComNum) ComboBox_SetCurSel(hCombo,cbei.iItem);
    cbei.iItem++;
   }
   RegCloseKey(hKey);
  }
  SetupDiDestroyDeviceInfoList(devs);
 }
}

/********************
 * Registry-Zugriff *
 ********************/

void config_t::LoadConfig(HWND Wnd) {
 HKEY key;
 if (RegOpenKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\smc2"),
   0,KEY_QUERY_VALUE,&key)) return;
 DWORD size=sizeof(*this);
 if (!RegQueryValueEx(key,T("Config"),NULL,NULL,(LPBYTE)this,&size)) {
  WINDOWPLACEMENT wp;
  InitStruct(&wp,sizeof(wp));
  GetWindowPlacement(Wnd,&wp);
  OffsetRect(&wp.rcNormalPosition,
    winpos.x-wp.rcNormalPosition.left,
    winpos.y-wp.rcNormalPosition.top);
  SetWindowPlacement(Wnd,&wp);
 }
 RegCloseKey(key);
}

void config_t::SaveConfig(HWND Wnd) {
 HKEY key;
 if (RegCreateKeyEx(HKEY_CURRENT_USER,T("Software\\h#s\\smc2"),
   0,NULL,REG_OPTION_NON_VOLATILE,KEY_SET_VALUE,NULL,&key,NULL)) return;
 RegSetValue(key,NULL,REG_SZ,StdMBoxTitle,(_tcslen(StdMBoxTitle)+1)*sizeof(TCHAR));
 WINDOWPLACEMENT wp;
 InitStruct(&wp,sizeof(wp));
 GetWindowPlacement(Wnd,&wp);
 winpos.x=(short)wp.rcNormalPosition.left;
 winpos.y=(short)wp.rcNormalPosition.top;
 RegSetValueEx(key,T("Config"),0,REG_BINARY,(LPBYTE)this,sizeof(*this));
 RegCloseKey(key);
}

/**************************
 * Dialog-Objekt-Routinen *
 **************************/

void GetWindowTextU(HWND w, char*s, int slen) {
 GetWindowText(w,(PTSTR)s,slen/sizeof(TCHAR));
 toUTF8(s,slen);
}

void SetWindowTextU(HWND w, char*s, int buflen) {
 fromUTF8(s,buflen);
 SetWindowText(w,(PTSTR)s);
}

void CDLG::SetValue(HWND Wnd, long v, int e) {
 char s[32];
 double f=Eeprom.m[c.MotorNum].PerUnit;
 long k=Eeprom.h.fv;
 switch (e) {
  case 0: k=1; break;
  case 1: {
   switch (v) {
    case -32768: v= LNAN; break;
    case -32767: v=-LINF; break;
    case +32767: v= LINF; break;
   }
  }break;
  case 2: {
   k=k*k>>-Eeprom.h.ea;
   switch (v) {
    case -128: v= LNAN; break;
    case -127: v=-LINF; break;
    case +127: v= LINF; break;
   }
  }break;
 }
 f/=k;
 const char *p=infstr(v);
 if ((c.ViewMode&3)==1 && !e) v>>=Eeprom.h.xs;		// Nur die Positionen auf Ganzschritte reduzieren
 switch (c.ViewMode&7) {
  case 1: if (p) goto infinite;
  nobreak;
  case 2: _snprintf(s,sizeof s,"%d",v); break;

  case 5: if (p) goto infinite;
  if (v<0) _snprintf(s,elemof(s),"%c%X",*sNegativeSign,-v);
  else _snprintf(s,sizeof s,"%X",v); break;

  case 6:
  if (e) v&=(1<<(1<<(5-e)))-1;				// Überflüssige FFFFs entfernen
  _snprintf(s,sizeof s,"%0*X",1<<(3-e),v); break;	// Mit fester Stellenzahl (8-4-2-1) ausgeben

  default: if (p) goto infinite;
  _snprintf(s,sizeof s,"%.*f",nk[e],v/f); pk(s); break;

  infinite: _snprintf(s,sizeof s,"%s",p);
 }
 SetWindowTextU(Wnd,s,sizeof s);
}

// Wert lesen / Gültigkeit überprüfen / speichern
// e = Exponent der Sekunden, daher e==0 für Position,
// e==1 für Geschwindigkeit, e==2 für Beschleunigung, e==3 für Ruck
// Bei e==1 ist p vom Typ short*, bei e>1 vom Typ char*!
// Das Ergebnis ist von <ViewMode> sowie <Eeprom..PerUnit,xs,fv> abhängig
bool CDLG::GetValue(HWND w, long*p, int e) {
 char s[16];
 GetWindowTextU(w,s,sizeof s);
 long v;
 for (v=0; v<3; v++) if (!_stricmp(s,Inf[v])) {	// Unendlich und NaN auswerten
  if (p) switch (e) {
   case 0: v-=0x7FFFFFFE; break;
   case 1: v-=0x7FFE; break;
   default: v-=0x7E;
  }
  goto okay;
 }
 char*q;
 if (c.ViewMode&3) {
  v=strtol(s,&q,c.ViewMode&4?16:10);
  if (*q) return false;
  if ((c.ViewMode&3)==1 && !e) v<<=Eeprom.h.xs;	// Bei Positionangaben Null-Bits einschieben
 }else{
  double f=strtod(s,&q);
  if (*q) return false;
  long k=Eeprom.h.fv;
  switch (e) {
   case 0: k=1; break;
   case 1: break;
   default: k=k*k>>-Eeprom.h.ea;
  }
  v=(long)rndint64(f/k*Eeprom.m[c.MotorNum].PerUnit);
 }
okay:
 if (p) switch (e) {
  case 0: *p=v; break;
  case 1: *(short*)p=(short)v; break;
  case 2: *(char*)p=(char)v; break;
  case 3: *(BYTE*)p=*(BYTE*)p&0xF0|v&0x0F; break;	// Low-Nibble setzen
 }
 return true;
}

void CDLG::SetUnit(HWND Wnd, int e) {
 char s[16];
 if (!(c.ViewMode&3)) {
  const char*p=Eeprom.m[c.MotorNum].Unit;	// in UTF-8
  memcpy(s,p,8); s[8]=0;			// nullterminierten String erzeugen
 }else s[0]=0;					// TODO: Faktoren bei e!=0 dazuschreiben
 if (e) {
  strcat(s,"/s");
  if (e>1) strcat(s,e==2?"²":"³");		// UTF8-Hochstellungen
 }
 SetWindowTextU(Wnd,s,sizeof s);
}

// m = was sich ändert:
//  Bit 0 = Erstinitialisierung
//  Bit 1 = Motor-Nummer
//  Bit 2 = Einheiten
//  Bit 3 = Timer
void CDLG::Page0Change(BYTE m) {
 HWND hPage=p.page[0].hWnd;
 if (!hPage) return;
 if (hPage!=p.hwndDisplay) return;
 int n=Eeprom.h.nm&0x0F;			// Anzahl Achsen
 bool validmotor=(unsigned)c.MotorNum<(unsigned)n;
 if (m&5 && (validmotor || (c.ViewMode&3)==2)) {
  SetAccel(hPage,16,c.soll.accel);
  SetSpeed(hPage,18,c.soll.speed);
  SetPos  (hPage,52,c.soll.pos);
  SetUnit (hPage,17,2);
  SetUnit (hPage,19,1);
  SetUnit (hPage,54,0);
 }
 if ((unsigned)c.MotorNum>=(unsigned)n) return;	// ungültiger Index!
 if (m&3) {
  const char *p=(char*)&Eeprom.m[n];		// hinter alle Achskonstanten
  for (int i=c.MotorNum; i; i--) p+=strlen(p)+1;	// vorrücken (UTF8-String-Liste)
  char s[64];
  strncpy(s,p,sizeof s);
  fromUTF8(s,sizeof s);
  SetDlgItemText(hPage,43,(PTSTR)s);
 }
 if (m&1) {
  SetCheckboxGroup(hPage,20,22,c.soll.flags);
 }
 if (m&9) {
  char cmd='A'+c.MotorNum;
  move_t m;
  Query(&cmd,1,&m,8);
  SetCheckboxGroup(hPage,32,36,m.flags);
  SetAccel(hPage,40,m.accel);
  SetSpeed(hPage,41,m.speed);
  SetPos  (hPage,42,m.pos);
  struct{
   BYTE cmd;
   BYTE len2;
   BYTE ofs;
   BYTE len;
  }q={0x90+c.MotorNum,2,8,1};
  if (Query(&q,sizeof q, &cmd, 1)==1) {
   CheckDlgButton(hPage,37,cmd>>1&1);
  }
 }
}

void CDLG::IncRamWriteOffsetTo(BYTE m) {
 if (RamWriteOffset<m) RamWriteOffset=m;
 EnableWindow(GetDlgItem(p.page[1].hWnd,3),TRUE);
}

void CDLG::Page1Change(BYTE m) {
 HWND hPage=p.page[1].hWnd;
 if (!hPage) return;
 if (hPage!=p.hwndDisplay) return;
 int n=Eeprom.h.nm&0x0F;			// Anzahl Achsen
 if ((unsigned)c.MotorNum>=(unsigned)n) return;	// ungültiger Index!
 if (m&7) {
  struct{
   BYTE cmd;
   BYTE len2;
   BYTE ofs;
   BYTE len;
  }q={0x90+c.MotorNum,2,8,sizeof ram};
  if (Query(&q,sizeof q, &ram, sizeof ram)==sizeof ram) {
   SetAccel(hPage,17,ram.MaxAccel);
   SetSpeed(hPage,21,ram.MaxSpeed);
   SetPos  (hPage,25,ram.Soll);
   SetPos  (hPage,29,ram.Anfang);
   SetPos  (hPage,33,ram.Ende);
   SetPos  (hPage,37,ram.Pilger);
   SetJerk (hPage,41,ram.Jerk&0x0F);
   RamWriteOffset=0;	// Schreiboffset
   EnableWindow(GetDlgItem(hPage,3),FALSE);
  }
  SetUnit(hPage,19,2);
  SetUnit(hPage,23,1);
  SetUnit(hPage,27,0);
  SetUnit(hPage,31,0);
  SetUnit(hPage,35,0);
  SetUnit(hPage,39,0);
  SetUnit(hPage,43,3);
 }
 if (m&0x10) {
  char s[16];
  _snprintf(s,sizeof s,"%02X",ram.flags);
  SetWindowTextU(GetDlgItem(hPage,64),s,sizeof s);
//  SetCheckboxGroup(hPage,66,73,ram.flags);
 }
}

//#define LOG2E 1.4426950408889634
struct Double{
 Double(double v):val(v){}
 operator double()		const	{return val;}
 Double& operator <<= (int exp)		{val=ldexp(val,exp); return *this;}
 double  operator <<  (int exp)	const	{return ldexp(val,exp);}
 Double& operator >>= (int exp)		{return *this<<=-exp;}
 double  operator >>  (int exp)	const	{return *this<<-exp;}
 Double& operator /= (int n)		{val/=n; return *this;}
 Double& operator %=(double n)		{val=fmod(val,n); return *this;}
 double  operator % (double n)	const	{return fmod(val,n);}
 Double& operator ^=(double n)		{val=pow(val,n); return *this;}
 double  operator ^ (double n)	const	{return pow(val,n);}
 double  operator & (double n)	const	{return hypot(val,n);}	// Betrag des Vektors
 double  operator | (double n)	const	{return 1/(1/val+1/n);}	// Parallelschaltung (von Widerständen)
 double  operator ~ ()		const	{return 1/val;}		// Inversion
private: double val;
};

void CDLG::OnMotorChange() {
 EeCtrl  &h=Eeprom.h;
 EeMotor &e=Eeprom.m[c.MotorNum];
 Double   u=fabs(e.PerUnit);
 nka=0;
 nk[0]=(char)ceil(log10(u>>h.xs));	if (nk[0]<0) nk[0]=0;
 u/=h.fv;
 nk[1]=(char)ceil(log10(u));		if (nk[1]<0) nk[1]=0;
 u/=h.fv;
 nk[2]=(char)ceil(log10(u<<h.ea));	if (nk[2]<0) nk[2]=0;
 nk[3]=nk[2];		// Vorschrift noch undefiniert für Ruck
 Page0Change();		// alles
 Page1Change();
}

// alle 100 ms
void CDLG::PeriodicAction() {
 load_t load;
 if (Query("%",1,&load,4)==4) {
  EeCtrl &h=Eeprom.h;
  TCHAR s[8];
  _sntprintf(s,elemof(s),T("%d %%"),trafo(load.min,h.le,h.lf,0,100));
  SetDlgItemText(ghMainWnd,23,s);
  _sntprintf(s,elemof(s),T("%d %%"),trafo(load.max,h.le,h.lf,0,100));
  SetDlgItemText(ghMainWnd,24,s);
  _sntprintf(s,elemof(s),T("%d %%"),trafo(load.avg,h.le<<8,h.lf<<8,0,100));
  SetDlgItemText(ghMainWnd,25,s);
 }
 Page0Change(8);
 Page1Change(8);
}

bool CDLG::Fahrt(bool reffahrt) {
 struct{
  BYTE cmd;
  BYTE len;
  move_t m;
 }m;
 m.cmd=0x80+c.MotorNum;
 m.len=8;
 m.m=c.soll;
 m.m.flags=reffahrt ? m.m.flags<<1&0x0C|2 : m.m.flags<<2&4|1;	// korrigieren
 return Query(&m,sizeof m,NULL,0)==0;
}

struct INCDEC{
 char *s,*endp1,*p,*caret;
 void Inc();
 bool Dec();
};

// Funktioniert auch mit Ausdrücken wie -123.45E-67
// Inkrementiert *Betrag* der Mantisse oder des Exponents
// String wird ggf. länger (bspw. bei 999 -> 1000)
void INCDEC::Inc() {
 if (s==p) {
insertone:
  memmove(p+1,p,(endp1-p-1)*sizeof(TCHAR));
  *p='1';
  caret++;
  return;
 }
 char c=*--p;
 if (strchr(sDecimal,c)) Inc();
 else if (!_istdigit(c)) goto insertone;
 else if (c=='9') {
  *p='0';
  Inc();
 }else *p=++c;
}

// Dekrementiert *Betrag* der Mantisse oder des Exponents
// String wird ggf. kürzer (bspw. 1000 -> 999 oder -1 -> 0) oder länger (0 -> -1)
// Vorzeichenwechsel wird mit Rückgabe <true> angezeigt (d.h. ab da mit "Inc()" fortfahren)
bool INCDEC::Dec() {
 if (s==p) return false;
 char c=*--p;
 if (strchr(sDecimal,c)) Dec();
 else if (c=='0') {
  *p='9';
  Dec();
 }else{
  *p=--c;
  if (c=='0' && _istdigit(p[1]) && !_istdigit(p[-1])) {
   memmove(p,p+1,(endp1-p-1)*sizeof(TCHAR));
   caret--;
  }
 }
 return false;
}

static void IncDec(char*s, int buflen, int&caret, int delta) {
 if (!*s) return;
 if (!delta) return;
 INCDEC incdec={s,s+buflen,NULL,s+caret};
 char *pSign=strpbrk(s,sNegativeSign);
 if (pSign && pSign>incdec.caret) pSign=NULL;	// ohne Beachtung
 if (pSign) delta=-delta;
 if (delta>0) do{
  incdec.p=incdec.caret;
  incdec.Inc();
 }while(--delta);
 else do{
  incdec.p=incdec.caret;
  incdec.Dec();
 }while(++delta);
 caret=incdec.caret-s;
}

static WNDPROC DefEditProc;

static LRESULT MyEditProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 if (msg==EM_SETSEL && wParam==0 && lParam==-1) return 0;
 LRESULT r=CallWindowProc(DefEditProc,Wnd,msg,wParam,lParam);
 if (msg==WM_GETDLGCODE)
  r&=~DLGC_HASSETSEL;
 return r;
}

static BOOL Page0Proc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 CDLG *o=(CDLG*)GetWindowLong(GetParent(Wnd),DWL_USER);
 switch (msg) {
  case WM_INITDIALOG: {
   o->p.page[0].hWnd=o->p.hwndDisplay=Wnd;
   o->Page0Change();
   HWND hEdit=GetDlgItem(Wnd,52);
   DefEditProc=SubclassWindow(hEdit,MyEditProc);
  }break;

  case WM_COMMAND: switch (wParam) {
   case MAKELONG(16,EN_UPDATE): {
    o->GetAccel((HWND)lParam,o->c.soll.accel);
   }break;
   case MAKELONG(18,EN_UPDATE): {
    o->GetSpeed((HWND)lParam,o->c.soll.speed);
   }break;
   case 20:
   case 21:
   case 22: o->c.soll.flags^=1<<(wParam-20); break;	// Flags
   case MAKELONG(52,EN_UPDATE): {
    o->GetPos((HWND)lParam,o->c.soll.pos);
   }break;

   case 48: o->Fahrt(true); break;	// Referenzfahrt

   case 49: {				// Referenzierung aufheben
    BYTE cmd='u'+o->c.MotorNum;
    Query(&cmd,1,NULL,0);
   }break;

   case 50: {				// Position nullen
    BYTE cmd='n'+o->c.MotorNum;
    Query(&cmd,1,NULL,0);
   }break;

   case 51: {				// Einzelmotor abbremsen
    BYTE cmd=20+o->c.MotorNum;
    Query(&cmd,1,NULL,0);
   }break;

   case 55: o->Fahrt(false); break;	// Zielfahrt
    
  }break;

  case WM_NOTIFY: {
   NMHDR *nm=(NMHDR*)lParam;
   switch (nm->idFrom) {
    case 53: switch (nm->code) {
     case UDN_DELTAPOS: {
      NMUPDOWN *ud=(NMUPDOWN*)nm;
      HWND hEdit=(HWND)SendMessage(ud->hdr.hwndFrom,UDM_GETBUDDY,0,0);
      char s[32];
      GetWindowTextU(hEdit,s,sizeof s);
      int caret;
      SendMessage(hEdit,EM_GETSEL,NULL,(LPARAM)&caret);
      IncDec(s,sizeof s,caret,-ud->iDelta);
      SetWindowTextU(hEdit,s,sizeof s);
      SendMessage(hEdit,EM_SETSEL,caret,caret);
      SetWindowLong(Wnd,DWL_MSGRESULT,1);
     }return TRUE;
    }break;
   }
  }break;

 }
 return FALSE;
}

static BOOL Page1Proc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 CDLG *o=(CDLG*)GetWindowLong(GetParent(Wnd),DWL_USER);
 switch (msg) {
  case WM_INITDIALOG: {
   o->p.page[1].hWnd=o->p.hwndDisplay=Wnd;
   o->Page1Change();
  }break;

  case WM_COMMAND: switch (wParam) {
   case MAKELONG(1,BN_CLICKED): {
    o->Page1Change();
   }break;
   case MAKELONG(3,BN_CLICKED): {
    struct{
     BYTE cmd;
     BYTE len;
     ram_t ram;
    }q;
    q.cmd=0x88+o->c.MotorNum;
    q.len=2+o->RamWriteOffset;
    q.ram=o->ram;		// Strukturkopie
    Query(&q,sizeof q,NULL,0);
   }break;
   case 64: {
    char s[32];
    GetWindowTextU((HWND)lParam,s,sizeof s);
    o->ram.flags=(BYTE)strtol(s,NULL,16);
    SetCheckboxGroup(Wnd,66,73,o->ram.flags);
   }
   case 66:
   case 68:
   case 69:
   case 70:
   case 71:
   case 72:
   case 73: {
    o->ram.flags^=1<<(wParam-66);
    o->Page1Change(0x10);
    o->IncRamWriteOffsetTo(offsetof(ram_t,flags)+sizeof(o->ram.flags));
   }break;
  }break;
 }
 return FALSE;
}

static BOOL MainDlgProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
 CDLG *o=(CDLG*)GetWindowLong(Wnd,DWL_USER);
 switch (msg) {
  case WM_INITDIALOG: {
   o=(CDLG*)lParam;
   SetWindowLong(Wnd,DWL_USER,lParam);
   o->p.hwndDlg=Wnd;
   SendDlgItemMessage(Wnd,16,CBEM_SETIMAGELIST,0,(LPARAM)SetupDiClassImageList.ImageList);
   SendMessage(Wnd,WM_WININICHANGE,0,0);
   o->c.LoadConfig(Wnd);
   SetDlgItemInt(Wnd,21,o->c.MotorNum,FALSE);
   CheckDlgButton(Wnd,80+(o->c.ViewMode&3),TRUE);
   if (o->c.ViewMode&4) CheckDlgButton(Wnd,83,TRUE);	// Hexadezimal-Bit
   if (!(o->c.ViewMode&3)) EnableWindow(GetDlgItem(Wnd,83),FALSE);
   HWND cb=GetDlgItem(Wnd,16);
   SerComboUpdate(cb);
   SendMessage(Wnd,WM_COMMAND,MAKELONG(16,CBN_SELCHANGE),(LPARAM)cb);
   o->p.hwndTab=GetDlgItem(Wnd,30);
   o->p.nPages=3;
   o->p.page[0].szTemplate=MAKEINTRESOURCE(101);
   o->p.page[0].dlgProc=Page0Proc;
   o->p.page[1].szTemplate=MAKEINTRESOURCE(102);
   o->p.page[1].dlgProc=Page1Proc;
   o->p.page[2].szTemplate=MAKEINTRESOURCE(103);
   o->p.OnInit();
  }return TRUE;

  case WM_DEVICECHANGE: {
   SetTimer(Wnd,1,1500,NULL);	// aktualisieren lassen
  }break;

  case WM_WININICHANGE: {
   GetLocaleInfoA(LOCALE_USER_DEFAULT,LOCALE_SDECIMAL,sDecimal,sizeof sDecimal);
   sDecimal[1]='.';
   sDecimal[2]=',';
   sDecimal[3]=0;
   GetLocaleInfoA(LOCALE_USER_DEFAULT,LOCALE_SNEGATIVESIGN,sNegativeSign,sizeof sNegativeSign);
   sNegativeSign[1]='-';
   sNegativeSign[2]=0;
#ifdef UNICODE
   const char *p="\342\210\236";
#else
   const char *p="INF";
#endif
   _snprintf(Inf[0],8,"%s%s",sNegativeSign,p);
   Inf[0][0]=sNegativeSign[0];
   _snprintf(Inf[1],8,"NaN");
   _snprintf(Inf[2],8,"+%s",p);
  }break;

  case WM_TIMER: switch (wParam) {
   case 1: {
    KillTimer(Wnd,wParam);
    SerComboUpdate(GetDlgItem(Wnd,16));
   }break;
   case 2: o->PeriodicAction(); break;
  }break;

  case WM_ENDSESSION: {
   if (wParam) o->c.SaveConfig(Wnd);
  }break;

  case WM_CLOSE: {
   o->c.SaveConfig(Wnd);
   if (hCom) CloseHandle(hCom);
   EndDialog(Wnd,wParam);
  }break;

  case WM_COMMAND: switch (wParam) {
   case IDCANCEL: {
    BYTE cmd='c';
    Query(&cmd,1,NULL,0);		// Alle Achsen stoppen
   }break;

   case MAKELONG(16,CBN_SELCHANGE): {
    KillTimer(Wnd,2);
    if (hCom) CloseHandle(hCom);
    COMBOBOXEXITEM cbei;
    cbei.mask=CBEIF_LPARAM;
    cbei.iItem=ComboBox_GetCurSel((HWND)lParam);
    if (cbei.iItem>=0) {
     SendMessage((HWND)lParam,CBEM_GETITEM,0,(LPARAM)&cbei);
     main.c.ComNum=(BYTE)cbei.lParam;
     hCom=ComOpen(cbei.lParam);
     if (hCom) {
      QueryBasicParams();
      o->OnMotorChange();
      SetTimer(Wnd,2,100,NULL);
     }
    }
   }break;

   case MAKELONG(21,EN_UPDATE): if (o) {
    o->c.MotorNum=GetDlgItemInt(Wnd,21,NULL,FALSE);
    if (hCom) o->OnMotorChange();
   }break;

   case 80:
   case 81:
   case 82: {
    o->c.ViewMode=o->c.ViewMode&0xFC|wParam-80;
    EnableWindow(GetDlgItem(Wnd,83),o->c.ViewMode&3?TRUE:FALSE);
    o->ViewModeChanged();
   }break;
   case 83: {
    o->c.ViewMode^=0x04;
    o->ViewModeChanged();
   }break;

  }break;

  case WM_NOTIFY: {
   NMHDR *n=(NMHDR*)lParam;
   switch (n->idFrom) {
    case 30: switch (n->code) {
     case TCN_SELCHANGE: {
      o->p.OnSelChange();
     }break;
    }break;
   }
  }break;
 }
 return FALSE;
}

EXTERN_C void CALLBACK WinMainCRTStartup() {
 WNDCLASS wc={
  CS_DBLCLKS,
  DefDlgProc,
  0,
  DLGWINDOWEXTRA,
  ghInstance=GetModuleHandle(NULL),
  LoadIcon(ghInstance,MAKEINTRESOURCE(1)),
  LoadCursor(0,IDC_ARROW),
  (HBRUSH)(COLOR_BACKGROUND+1),
  NULL,
  T("smc2")
 };
 RegisterClass(&wc);
 InitCommonControls();
 LoadString(ghInstance,1,StdMBoxTitle,elemof(StdMBoxTitle));
 SetupDiClassImageList.cbSize=sizeof(SetupDiClassImageList);
 SetupDiGetClassImageList(&SetupDiClassImageList);
 ExitProcess(DialogBoxParam(0,MAKEINTRESOURCE(100),0,MainDlgProc,(LPARAM)&main));
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded