/* Hochregallager-Demo mit 3 Schrittmotoren sowie Schrittmotorsteuerung am Parallelport
* Schaltplan siehe Eagle „Schrittmotorkarte.sch“
* Zum Programmierstil siehe http://www.tu-chemnitz.de/~heha/hs_freeware/mein_msvc.htm
* h#s 06/07
Zu tun:
*/
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501 // WM_THEMECHANGED aktivieren
#include <windows.h>
#include <windowsx.h> // Makros
#include <commdlg.h> // Datei öffnen/speichern
#include <commctrl.h> // Listenfenster für die Zeitschritte
#include <shlwapi.h> // nützliche Funktionen
#include <mmsystem.h>
#define elemof(x) (sizeof(x)/sizeof((x)[0]))
#define T(x) TEXT(x)
#define nobreak
//Leider hat Win95 mit IE 3.0 ein reduziertes Angebot der
//shlwapi.dll, kein StrCatBuff, kein wnsprintf, kein wvnsprintf,
//daher Ersetzung durch „unsichere“ Funktionen aus kernel32.dll
//Weiterhin kennt Win95 die Schrift "MS Shell Dlg 2" nicht...
#ifdef WIN95
# undef StrCatBuff
# define StrCatBuff(d,s,l) lstrcat(d,s)
# define wnsprintf1(d,l,t,a1) wsprintf(d,t,a1)
# define wnsprintf2(d,l,t,a1,a2) wsprintf(d,t,a1,a2)
# define wnsprintf3(d,l,t,a1,a2,a3) wsprintf(d,t,a1,a2,a3)
# define wnsprintf6(d,l,t,a1,a2,a3,a4,a5,a6) wsprintf(d,t,a1,a2,a3,a4,a5,a6)
# undef wvnsprintf
# define wvnsprintf(d,l,t,a) wvsprintf(d,t,a)
#else
# define wnsprintf1 wnsprintf
# define wnsprintf2 wnsprintf
# define wnsprintf3 wnsprintf
# define wnsprintf6 wnsprintf
#endif
#ifdef DEBUG
# define _debug(x) DebugPrintf x
void _cdecl DebugPrintf(LPCTSTR s,...) {
TCHAR buf[256];
wvnsprintf(buf,elemof(buf),s,(va_list)(&s+1));
OutputDebugString(buf);
}
#else
# define _debug(x)
#endif
#ifdef WIN32
# define Send_WM_Command(ToWnd,CtlId,NotifyCode,FromWnd)\
SendMessage(ToWnd,WM_COMMAND,MAKELONG(CtlId,NotifyCode),(LPARAM)FromWnd);
#else // Win16
# define Send_WM_Command(ToWnd,CtlId,NotifyCode,FromWnd)\
SendMessage(ToWnd,WM_COMMAND,CtlId,MAKELONG((UINT)FromWnd,NotifyCode));
#endif
// Deklaration der zwei Einsprünge in (statisch gebundene) InpOut32.dll
EXTERN_C void _declspec(dllimport) WINAPI Out32(WORD,BYTE);
EXTERN_C BYTE _declspec(dllimport) WINAPI Inp32(WORD);
/*****************************************
* Globale Variablen und Typdefinitionen *
*****************************************/
WORD gLptBase=0x378; // Voreinstellung
HINSTANCE ghInstance;
HWND ghMainWnd;
UINT gEditErrors; // Bit pro Editfenster für Fehler (Hintergrund rot)
const TCHAR gHelpFileName[]=T("Regal.hlp");
static HWND gToolTip;
struct{
HBRUSH hbrError; // rot (Fehler im Editfenster)
}gGdiObj;
TCHAR StdMBoxTitle[64];
/****************
* aus WUTILS.C *
****************/
//Win32-typische Strukturen mit DWORD-Ausrichtung initialisieren
void _fastcall InitStruct(LPVOID p, UINT len) {
LPUINT q=(LPUINT)p;
*q=len; len/=sizeof(UINT); len--;
if (len) do *++q=0; while (--len);
}
int _cdecl MBox(HWND Wnd, LPCTSTR Text, UINT Type, ...) {
TCHAR buf[256],buf2[256];
if (!HIWORD(Text)) {
LoadString(ghInstance,(UINT)(DWORD_PTR)Text,buf2,elemof(buf2));
Text=buf2;
}
wvnsprintf(buf,elemof(buf),Text,(va_list)(&Type+1));
return MessageBox(Wnd,buf,StdMBoxTitle,Type);
}
/************************************************
* LED-Darstellung (etwa wie LptChk, WinDriver) *
************************************************/
void MakeGdiObj(void) {
gGdiObj.hbrError=CreateSolidBrush(RGB(255,128,128));
}
void KillGdiObj(void) {
DeleteBrush(gGdiObj.hbrError);
}
/**************************
* Schrittmotoren steuern *
**************************/
struct TMotor{
int Ist,Soll;
int Anfang,Ende; // nur für Scrollbar
int Offset; // Nullpunktverschiebung (nur für Anzeige)
char InSync;
char InMove; // svw. Geschwindigkeit (vzb.)
bool synced;
BYTE mask; // 1 << Motornummer
void DoneStep();
void Stop();
void Move(int Pos);
void Sync(char Dir);
static void ReadStatusport();
static void CALLBACK Step3(UINT,UINT,DWORD_PTR,DWORD_PTR,DWORD_PTR);
static void Init();
static void Done();
};
TMotor Motor[3];
static UINT gTimerId;
static volatile BYTE dataport=0x18,statusport,dir_reg=0x18;
// Bitbelegung <dataport>
// 0 - Enable Motor 0 (X) - Richtung Motor 0 (X)
// 1 - Enable Motor 1 (Y) - Richtung Motor 1 (Y)
// 2 - Enable Motor 2 (Z) - Richtung Motor 2 (Z)
// 3 - Schritt (alle freigeschaltenen Motoren)
// 4 - globales Enable
// 5 - Strobe für Richtungsregister (L-H-Flanke)
// Bitbelegung <statusport>
// 7 - Motor 0 (X) durch passenden Endschalter freigegeben (BSY)
// 5 - Motor 1 (Y) durch passenden Endschalter freigegeben (PE)
// 4 - Motor 2 (Z) durch passenden Endschalter freigegeben (SEL)
// In der Software "passend" auf Bit 0..2 umgesetzt
// Achtung! Ausführung im Multimedia-Thread!
void TMotor::DoneStep() {
if (dataport&mask) {
if (InSync) {
if (synced) Ist+=InSync;
if (!(statusport&mask)) {
InSync=0;
if (!synced) Soll=Ist=0;
synced=true;
}
}else{
Ist+=InMove;
if (Ist==Soll) {
dataport&=~mask;
InMove=0;
}
}
}
}
void TMotor::ReadStatusport() {
BYTE b=Inp32(gLptBase+1)&0xB0;
if (!(b&0x80)) b|=1;
if (b&0x20) b|=2;
if (b&0x10) b|=4;
statusport=b;
}
// Achtung! Ausführung im Multimedia-Thread!
void CALLBACK TMotor::Step3(UINT TimerID, UINT Msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) {
static BYTE saved_dir_reg;
if (!(dataport&7)) return; // nichts zu tun
ReadStatusport();
if (saved_dir_reg!=dir_reg) {
saved_dir_reg=dir_reg;
Out32(gLptBase+0,dir_reg); // schaltet ENABLE kurzfristig aus
Out32(gLptBase+0,dir_reg|0x20); // Richtungsbit einschreiben
Out32(gLptBase+0,dir_reg);
Out32(gLptBase+0,dataport);
}
Out32(gLptBase+0,dataport&~8);
Sleep(1);
Out32(gLptBase+0,dataport); // L-H-Flanke macht Schritt(e)
Motor[0].DoneStep();
Motor[1].DoneStep();
Motor[2].DoneStep();
}
void TMotor::Init() {
// RtlZeroMemory(Motor,sizeof(Motor));
// Kennwerte der Hochregallager-Demo (mech. Aufbau)
Motor[0].mask=1; Motor[0].Ende=22050;
Motor[1].mask=2; Motor[1].Ende=12590;
Motor[2].mask=4; Motor[2].Ende=3330;
timeBeginPeriod(1);
gTimerId=timeSetEvent(2,1,Step3,0,TIME_PERIODIC); // 500 Hz
}
void TMotor::Done() {
timeKillEvent(gTimerId);
timeEndPeriod(1);
}
void TMotor::Stop() {
if (!InMove) return;
dataport&=~mask; // Motor deaktivieren
InMove=0;
}
void TMotor::Move(int Pos) {
if (Soll==Pos) return;
Stop();
Soll=Pos;
int i=this-Motor;
SendDlgItemMessage(ghMainWnd,10+i*10,TBM_SETTIC,0,i==1?-Pos:Pos);
if (Pos>Ist) {
InMove=1;
dir_reg&=~mask;
}else if (Pos<Ist){
InMove=-1;
dir_reg|=mask;
}
dataport|=mask; // Motor und global aktivieren
}
void TMotor::Sync(char Dir) {
InSync=Dir;
if (Dir>0) {
dir_reg&=~mask;
dataport|=mask;
}else if (Dir<0) {
dir_reg|=mask;
dataport|=mask;
}else dataport&=~mask; // anhalten mit Dir=0
}
/***********************
* Laden und Speichern *
***********************/
void GetDefaultsFileName(PTSTR Name, UINT len) {
PTSTR p;
GetModuleFileName(0,Name,len);
p=PathFindFileName(Name);
len-=(UINT)(p-Name);
lstrcpyn(p,T("Regal.ini"),len);
}
// alles mögliche per WritePrivateProfileString() schreiben
// Sonderfall Format == NULL: Value = String (svw. LPCTSTR)
// Sonderfall Format == Value == NULL: Eintrag löschen
// Achtung: Format == "%s": Value = Zeiger auf LPCTSTR!
BOOL vWriteProfile(LPCTSTR FileName, LPCTSTR Section, LPCTSTR Key, LPCTSTR Format, va_list Value) {
TCHAR buf[MAX_PATH];
if (Format) {
wvnsprintf(buf,elemof(buf),Format,Value);
Value=(va_list)buf;
}
return WritePrivateProfileString(Section,Key,(LPCTSTR)Value,FileName);
}
BOOL _cdecl WriteProfile(LPCTSTR FileName, LPCTSTR Section, LPCTSTR Key, LPCTSTR Format, ...) {
return vWriteProfile(FileName,Section,Key,Format,(va_list)(&Format+1));
}
void SaveDefaults(void) {
TCHAR FileName[MAX_PATH];
WINDOWPLACEMENT wp;
GetDefaultsFileName(FileName,elemof(FileName));
InitStruct(&wp,sizeof(wp));
GetWindowPlacement(ghMainWnd,&wp);
vWriteProfile(FileName,T("MainWnd"),T("WinPos"),T("%i,%i"),(va_list)&wp.rcNormalPosition);
WriteProfile(FileName,T("MainWnd"),T("LptBase"),T("0x%X"),gLptBase);
if (false) MBox(ghMainWnd,MAKEINTRESOURCE(37)/*Fehler*/,MB_OK,FileName);
}
// Kommaseparierte Integer-Liste in Array wandeln
int ScanInts(LPCTSTR s, int ints[], int elems) {
int i=0;
if (elems) do{
*ints++=StrToInt(s);
i++;
s=StrChr(s,',');
if (!s) break;
s++; // hinter Komma
}while (--elems);
return i; // Anzahl gewandelter Elemente
}
void LoadDefaults(void) {
TCHAR FileName[MAX_PATH];
TCHAR buf[MAX_PATH];
int val;
POINT p;
GetDefaultsFileName(FileName,elemof(FileName));
// Fensterposition und -größe lesen
if (GetPrivateProfileString(T("MainWnd"),T("WinPos"),
T(""),buf,elemof(buf),FileName)
&& ScanInts(buf,(int*)&p.x,2)==2) {
SetWindowPos(ghMainWnd,0,
p.x,p.y,0,0,SWP_NOSIZE|SWP_NOZORDER);
// Fenster in Bildschirm ziehen...
SendMessage(ghMainWnd,DM_REPOSITION,0,0);
}
if (GetPrivateProfileString(T("MainWnd"),T("LptBase"),
T(""),buf,elemof(buf),FileName)
&& StrToIntEx(buf,STIF_SUPPORT_HEX,&val)
&& val>0x100
&& val<=0xFFFF) gLptBase=(WORD)val;
}
/***********************
* 2 Dialog-Prozeduren *
***********************/
BOOL CALLBACK SetupDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_INITDIALOG: {
const static WORD DefLpt[3]={0x378,0x278,0x3BC};
HWND hCombo=GetDlgItem(Wnd,101);
TCHAR buf[16];
int i;
wsprintf(buf,T("%Xh"),gLptBase);
SetWindowText(hCombo,buf); // Eingabezeile belegen
for (i=0; i<3; i++) {
wsprintf(buf,T("%Xh (LPT%u)"),DefLpt[i],i+1);
ComboBox_AddString(hCombo,buf);
if (DefLpt[i]==gLptBase) ComboBox_SetCurSel(hCombo,i);
} // Auswahl einer Voreinstellung, überschreibt Eingabezeile
}return TRUE;
case WM_COMMAND: switch (LOWORD(wParam)) {
case IDOK: {
TCHAR buf[8];
int value;
buf[0]='0'; buf[1]='x';
GetDlgItemText(Wnd,101,buf+2,elemof(buf)-2);
if (!StrToIntEx(buf,STIF_SUPPORT_HEX,&value) || value<0x100) {
Wnd=GetDlgItem(Wnd,101);
SetFocus(Wnd);
ComboBox_SetEditSel(Wnd,0,-1); // bei Fehler
break;
}
gLptBase=(WORD)value;
}nobreak;
case IDCANCEL: {
EndDialog(Wnd,wParam);
}break;
}break;
case WM_HELP: {
LPHELPINFO hi=(LPHELPINFO)lParam;
DWORD ids[4];
ids[0]=hi->iCtrlId;
ids[1]=MAKELONG(hi->iCtrlId,1101); // High-Teil = Ressourcen-ID des Dialogs
ids[2]=ids[3]=0;
WinHelp((HWND)hi->hItemHandle,gHelpFileName,HELP_WM_HELP,(DWORD_PTR)ids);
}break;
}
return FALSE;
}
struct TRegal{
static const int X0=1300;
static const int Y0=0;
static const int DX=3500; // Regalzellenabstand
static const int DY=3518;
static const int HY=200; // Hubweg
static const int ZE=3300; // Ende Gabelweg
int Ziel;
int Hub;
void MoveTo(int nr);
void beladen(bool state);
void PeriodicAction();
private:
int x,y,z;
}Regal;
void TRegal::MoveTo(int nr) {
if (Ziel==nr) return;
Ziel=nr;
if (Ziel<0) return;
if (Ziel==0) {
x=X0;
y=Y0;
}else{
x=X0+((Ziel-1)%5+1)*DX;
y=Y0+((20-Ziel)/5)*DY;
}
y+=Hub;
}
void TRegal::beladen(bool state) {
int h=state?HY:0;
if (Hub!=h) {
Motor[1].Move(h-Hub+Motor[1].Soll);
y+=h-Hub;
Hub=h;
}
}
void TRegal::PeriodicAction() {
if (Ziel<0) return;
if (Motor[0].InMove) return;
if (Motor[1].InMove) return;
if (Motor[2].InMove) return;
// Prüfen ob Zielgebiet erreicht
if (Motor[0].Ist==x && Motor[1].Ist==y) {
if (Motor[2].Soll!=ZE) {
Motor[2].Move(ZE);
SetDlgItemInt(ghMainWnd,31,ZE,TRUE);
}
return;
}
// Zielgebiet nicht erreicht, Gabel ziehen?
if (Motor[2].Soll!=0) {
Motor[2].Move(0);
SetDlgItemInt(ghMainWnd,31,0,TRUE);
return;
}
Motor[0].Move(x);
SetDlgItemInt(ghMainWnd,11,x,TRUE);
Motor[1].Move(y);
SetDlgItemInt(ghMainWnd,21,y,TRUE);
}
BOOL CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
static bool updating,dragging;
switch (Msg) {
case WM_INITDIALOG: {
int nr;
ghMainWnd=Wnd;
GetWindowText(Wnd,StdMBoxTitle,elemof(StdMBoxTitle));
gToolTip=CreateWindowEx(WS_EX_TOPMOST,TOOLTIPS_CLASS,NULL,
WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP|TTS_BALLOON,0,0,0,0,Wnd,0,ghInstance,NULL);
MakeGdiObj();
LoadDefaults(); // ruft InitLptState auf - nach dem Einlesen der Portadresse
TMotor::Init();
for (nr=0; nr<3; nr++) {
HWND w;
w=GetDlgItem(Wnd,nr*10+10);
if (nr==1) {
SendMessage(w,TBM_SETRANGEMIN,FALSE,-Motor[nr].Ende);
SendMessage(w,TBM_SETRANGEMAX,FALSE,0);
}else SendMessage(w,TBM_SETRANGEMAX,FALSE,Motor[nr].Ende);
SendMessage(w,TBM_SETPOS,TRUE,Motor[nr].Ist);
SendMessage(w,TBM_SETPAGESIZE,0,10);
}
Regal.MoveTo(-1); // ungültig machen
SetTimer(Wnd,1,100,NULL);
}return TRUE;
case WM_COMMAND: switch (LOWORD(wParam)) {
//Menü:
case 1005: { // Vorgabe speichern
SaveDefaults();
}break;
case 1009: { // Beenden
SendMessage(Wnd,WM_CLOSE,0,0);
}break;
case 1101: { // Einstellungen
DialogBox(ghInstance,MAKEINTRESOURCE(1101),Wnd,SetupDlgProc);
}break;
case 1901: { // Hilfe
WinHelp(Wnd,gHelpFileName,HELP_CONTENTS,0);
}break;
case 1909: { // Über
MBox(Wnd,MAKEINTRESOURCE(26),MB_OK);
}break;
//Dialogelemente:
case 12: Regal.MoveTo(-1); Motor[0].Sync(-1); break;
case 22: Regal.MoveTo(-1); Motor[1].Sync(-1); break;
case 32: Regal.MoveTo(-1); Motor[2].Sync(-1); break;
case 99: Regal.beladen(IsDlgButtonChecked(Wnd,wParam)!=0); break;
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
case 106:
case 107:
case 108:
case 109:
case 110:
case 111:
case 112:
case 113:
case 114:
case 115:
case 116:
case 117:
case 118:
case 119:
case 120: Regal.MoveTo(wParam-100); break;
}break;
case WM_HSCROLL:
case WM_VSCROLL: if (!updating){
int nr=GetDlgCtrlID((HWND)lParam)/10-1;
int pos=SendMessage((HWND)lParam,TBM_GETPOS,0,0);
if (nr==1) pos=-pos;
switch (LOWORD(wParam)) {
case TB_THUMBTRACK:
case TB_TOP:
case TB_BOTTOM:
case TB_LINEUP:
case TB_LINEDOWN:
case TB_PAGEUP:
case TB_PAGEDOWN: {
SetDlgItemInt(Wnd,nr*10+11,pos,TRUE); // Sollwert anzeigen
dragging=true;
}break;
case TB_THUMBPOSITION:
case TB_ENDTRACK: {
Regal.MoveTo(-1);
Motor[nr].Move(pos);
if (nr==1) pos=-pos;
// SendMessage((HWND)lParam,TBM_SETTIC,0,pos); // Sollwert als Strich anzeigen
dragging=false;
}break;
}
}break;
case WM_TIMER: switch (wParam) {
// Istpositions-Anzeige (10 Hz, periodisch)
case 1: {
updating=true;
if (!dragging) {
SendDlgItemMessage(Wnd,10,TBM_SETPOS,TRUE,Motor[0].Ist);
SendDlgItemMessage(Wnd,20,TBM_SETPOS,TRUE,-Motor[1].Ist);
SendDlgItemMessage(Wnd,30,TBM_SETPOS,TRUE,Motor[2].Ist);
if (!Motor[0].InMove) SendDlgItemMessage(Wnd,10,TBM_CLEARTICS,TRUE,0);
if (!Motor[1].InMove) SendDlgItemMessage(Wnd,20,TBM_CLEARTICS,TRUE,0);
if (!Motor[2].InMove) SendDlgItemMessage(Wnd,30,TBM_CLEARTICS,TRUE,0);
}
SetDlgItemInt(Wnd,13,Motor[0].Ist,TRUE);
SetDlgItemInt(Wnd,23,Motor[1].Ist,TRUE);
SetDlgItemInt(Wnd,33,Motor[2].Ist,TRUE);
CheckDlgButton(Wnd,14,statusport&1?1:0);
CheckDlgButton(Wnd,24,statusport&2?1:0);
CheckDlgButton(Wnd,34,statusport&4?1:0);
updating=false;
Regal.PeriodicAction();
}break;
// Editfenster-Validierung und Übernahme in die Liste
case 11:
case 13: {
int i;
BOOL b;
KillTimer(Wnd,wParam); // nicht-zyklisch!
i=GetDlgItemInt(Wnd,wParam,&b,TRUE);
}break;
}break;
case WM_CTLCOLOREDIT: {
int id=GetDlgCtrlID((HWND)lParam)-54;
if ((unsigned)id>=sizeof(gEditErrors)*8) break;
if (!(gEditErrors&(1<<id))) break;
SetBkMode((HDC)wParam,TRANSPARENT);
SetBkColor((HDC)wParam,RGB(255,128,128));
}return (BOOL)gGdiObj.hbrError;
case WM_HELP: {
LPHELPINFO hi=(LPHELPINFO)lParam;
DWORD ids[4];
ids[0]=hi->iCtrlId;
ids[1]=MAKELONG(hi->iCtrlId,100); // High-Teil = Ressourcen-ID des Dialogs
ids[2]=ids[3]=0;
WinHelp((HWND)hi->hItemHandle,gHelpFileName,HELP_WM_HELP,(DWORD)ids);
// HELP_WM_HELP positioniert besser als HELP_CONTEXTPOPUP
}break;
case WM_ENDSESSION: {
TMotor::Done();
SaveDefaults();
}break;
case WM_CLOSE: {
SendMessage(Wnd,WM_ENDSESSION,1,0);
WinHelp(Wnd,gHelpFileName,HELP_QUIT,0);
EndDialog(Wnd,0);
}break;
case WM_DESTROY: {
KillGdiObj();
}break;
}
return FALSE;
}
/******************************
* Hauptprogramm (lächerlich) *
******************************/
int _stdcall WinMainCRTStartup(void) {
WNDCLASS wc={
CS_HREDRAW|CS_VREDRAW,
DefDlgProc,
0, // ClsExtra
DLGWINDOWEXTRA,
ghInstance=GetModuleHandle(NULL), // hInstance
LoadIcon(ghInstance,MAKEINTRESOURCE(100)), // hIcon
LoadCursor(0,IDC_ARROW), // hCursor
(HBRUSH)(COLOR_WINDOW+1),
NULL,
T("Regal")}; // Klassenname (in Übereinstimmung mit Dialog-Ressource 100)
RegisterClass(&wc);
InitCommonControls();
ExitProcess(DialogBoxParam(0,MAKEINTRESOURCE(100),0,MainDlgProc,(LPARAM)GetCommandLine()));
}
Detected encoding: ASCII (7 bit) | 8
|