Source file: /~heha/messtech/motor.zip/VXDSRC/MPK3D.ASM

;Schrittmotor-Steuerung für ISEL-Karte MPK3
;UseVTD	equ	1		;Timer statt CMOS benutzen
;UseLoop	equ	1		;Schleife statt Summenformel benutzen
;LogInt	equ	1		;Jeden Interrupt loggen
UseTable equ	1		;0=Sinus, 1=Excel-Tabelle, 2=Roh-Tabelle

	.386			;32bit-Segmente aktivieren
	include tvmm.inc	;Generelles
	include	debug.inc	;wegen Debugging
IFDEF UseVTD
	include	vtd.inc		;Zeitgeber-Services
ELSE
	include	vpicd.inc	;Interrupt-Controller
ENDIF
MPK3D_Device_ID	equ	378Ch	;von Microsoft bestätigt:
;>You have been assigned a device ID of 378Ch for your MPK3D.386 virtual
;>device.  This number is uniquely assigned to this device.
MPK3D_Major_Ver	equ	1
MPK3D_Minor_Ver	equ	0

;=============================
bit	equ	<1 shl>
crlf	equ	<13,10>
;=============================
bres macro r,b:rest
	maskflag r,not (b)
endm
;=============================
bset macro r,b:rest
	setflag r,b
endm
;=============================
btst macro r,b:rest
	testflag r,b
endm
;=============================
dz macro str:rest
	db	str,0
endm
;=============================
ld macro r1,r2
	push	r2
	pop	r1
endm
;=============================
Debug_Halt macro
 ifdef DEBUG
	int	1
 endif
endm
;=============================
MIN macro r,c
local l1
	cmp	r,c
	jc	short l1
	mov	r,c
l1:
endm
;=============================
MAX macro r,c
local l1
	cmp	r,c
	jnc	short l1
	mov	r,c
l1:
endm
;=============================
RABS macro r
local l1
	or	r,r
	jns	short l1
	neg	r
l1:
endm
;=============================
ifdef DEBUG
	%OUT	!Assembliere Debugversion!
endif

BeginInterrupts macro
IFDEF UseVTD
	MASM
	mov	eax,1
	VxDcall	VTD_Begin_Min_Int_Period
	mov	esi,OFFSET32 intproc
	VMMCall	Set_Global_Time_Out
	IDEAL
ENDIF
	endm

EndInterrupts macro
IFDEF UseVTD
	MASM
	mov	eax,1
	VxDcall	VTD_End_Min_Int_Period
	IDEAL
ENDIF
	endm

	locals	@@

Declare_Virtual_Device  MPK3D, MPK3D_Major_Ver, MPK3D_Minor_Ver,\
			MPK3D_Control_Proc, MPK3D_Device_ID,\
			Undefined_Init_Order,MPK3D_API,MPK3D_API

;*********************************************************
;*** Obligatorischer VxD-Botschafts-Funktionsverteiler ***
;*********************************************************
VxD_Locked_Code_Seg
BeginProc MPK3D_Control_Proc
	Control_Dispatch Sys_Critical_Init, MPK3D_Init
	Control_Dispatch System_Exit, MPK3D_Exit
	clc
	ret
EndProc MPK3D_Control_Proc
VxD_Locked_Code_ends

;********************************************************
;*** Residenter Datenbereich und Konstantendefinition ***
;********************************************************
VxD_Locked_Data_Seg
	IDEAL

;Folgende Kommandos gibt es:
SM_Nop		equ	0	;"NOP"-Kommando
SM_Free		equ	1	;Motorspulen stromfrei schalten
SM_Sync		equ	2	;Referenzfahrt, EDX=Referenzpunkt
SM_MoveAbs	equ	3	;Absolutbewegung
SM_MoveRel	equ	4	;Relativbewegung
SM_Assign	equ	5	;Belegung, Motor stromfrei
SM_UnAssign	equ	6	;Freigabe, Motor stromfrei
SM_SetSA	equ	7	;Geschwindigkeit und Beschl. festlegen **)
SM_GetSA	equ	8	;Geschw. und Beschleunigung erfragen
SM_SetPostMsg	equ	9	;PostMessage-Adresse und Fenster festlegen **)
SM_SetPostMsg	equ	10	;obiges erfragen
SM_GetPosition	equ	11	;Position und Geschw. erfragen (EDX u. ECX)
SM_SetBounds	equ	12	;Begrenzungen setzen **)
SM_GetBounds	equ	13	;Begrenzungen erfragen
SM_Stop		equ	14	;Soforthalt
SM_SetGear	equ	15	;Übersetzungsverhältnis setzen **)
SM_GetGear	equ	16	;Übersetzungsverhältnis erfragen
SM_SetHWE	equ	17	;Hardware-Endschalter-Maske setzen
;**) EDX und ECX enthalten bei Rückkehr die _alten_ Werte
SM_LastFunc	equ	17
;{Fehlercodes von Isel}
ME_NoError	equ	0;
ME_WrongFunction equ	1;
ME_InMove	equ	4;
ME_SoftEnd	equ	5;
ME_HardEnd	equ	7;
ME_NoRef	equ	9;
;{Eigene Fehlercodes}
ME_NotAssigned	equ	50;
ME_AlreadyAssigned equ	51;
ME_NoDriver	equ	52;

INVPOS	equ	80000000h	;Ungültige Position = -MaxLong-1

IFDEF UseVTD
FREQ	equ	1000
ELSE
IrqHand		dd	?	;IRQ-Griff
FREQ	equ	1024
ENDIF
Frequency	dd	FREQ	;für Zeitmessungen usw.
upcounter	dd	?

struc TMotor
 Flags		dw	0
 HWEMask	db	?
 HWEXor		db	0
 PortA		db	?
 PortB		db	?
 HWPos		dw	0
 Pos		dd	INVPOS	;bedeutet "ungültige Position"
 union
  struc
   WegLo	dd	0	;(Rest-)Weg in Nanoschritten (2**-16 Mikroschr.)
   WegHi	dd	0	;(Rest-)Weg
  ends
  struc
   fill1	dw	?
   WegMid	dd	?
   fill2	dw	?
  ends
 ends
 Refpoint	dd	0	;Referenzpunkt nach Sync
 Speed		dd	0	;Momentangeschwindigkeit
 MaxSpeed	dd	48*65536;Maximalgeschwindigkeit
 RefSpeed	dd	12*65536;Referenz-Geschwindigkeit
 MaxAccel	dd	48	;Maximalbeschleunigung
 LeftBound	dd	-7FFFFFFFh	;Grenzen enthalten Anwenderkoordinaten!
 RightBound	dd	7FFFFFFFh
 Umrech1	dd	1
 Umrech2	dd	1
 CallbackAddr	dd	0
 CallbackWnd	dd	0
ends TMotor

MotDef		dd	48*65536,12*65536,48,-7FFFFFFFh,7FFFFFFFh,1,1
				;Defaults ab MaxSpeed
MotDefElems	equ	7	;7 Elemente initialisieren (Länge obiges DD)

PMotor	equ	(TMotor ebx)	;Objektzeiger ist immer EBX

MotX	TMotor	<0,4,4,0,1>
MotY	TMotor	<0,2,2,2,3>
MotZ	TMotor	<0,1,1,4,5>
;Mit den Bits scheint irgendetwas verdreht zu sein!

PortBase	dw	340h	;Port-Basisadresse
;Die Adresse 347h wird durch das VxD zu einem rücklesbaren Ausgabeport
;umfunktioniert, welches die Leistungsendstufe (Bit0) und das Relais
;(Bit1) steuert. Das Port 346h bleibt in seiner Funktionalität; Schreib-
;zugriffe wirken genauso wie auf 347h - verändern also "seinen" Spiegel.
;Die Motor-Phasenstrom-Ports sind einfach nur rücklesbar.
;Da das Gesamtverhalten dem Original entspricht, sollte auch die
;Originalsoftware laufen; allerdings werden die Portzugriffe zusätzlich
;durch das Trapping verlangsamt.
PortMirror	db	7 dup (?)	;7 Ausgabeport-Spiegel

;Sinus-Tabelle OHNE 0- und 1-Punkt, zentriert auf die Mitte zwischen 7F und 80
;Bedingt durch diese seltsame Zentrierung in der Hardware gibt es keinen
;100% stromfreien Zustand der Motorspule.
;Diese Sinustabelle enthält 256 Werte im Bereich 128..255 für den
;1. Quadranten. Die Quadrantenumrechnung erfolgt durch das Programm GetCos
;bzw. GetSin.
SinTab:
if UseTable eq 0
	db	128,129,130,131,132,132,133,134,135,135,136,137,138,139,139,140
	db	141,142,142,143,144,145,145,146,147,148,149,149,150,151,152,152
	db	153,154,155,155,156,157,158,158,159,160,161,161,162,163,164,164
	db	165,166,167,167,168,169,170,170,171,172,173,173,174,175,176,176
	db	177,178,178,179,180,181,181,182,183,183,184,185,185,186,187,188
	db	188,189,190,190,191,192,192,193,194,194,195,196,196,197,198,198
	db	199,200,200,201,201,202,203,203,204,205,205,206,206,207,208,208
	db	209,209,210,211,211,212,212,213,214,214,215,215,216,216,217,218
	db	218,219,219,220,220,221,221,222,222,223,223,224,224,225,225,226
	db	226,227,227,228,228,229,229,230,230,231,231,232,232,233,233,233
	db	234,234,235,235,236,236,236,237,237,238,238,238,239,239,239,240
	db	240,241,241,241,242,242,242,243,243,243,244,244,244,245,245,245
	db	245,246,246,246,247,247,247,247,248,248,248,248,249,249,249,249
	db	250,250,250,250,251,251,251,251,251,251,252,252,252,252,252,252
	db	253,253,253,253,253,253,253,254,254,254,254,254,254,254,254,254
	db	254,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255
elseif UseTable eq 1
	db	128,130,131,132,133,134,135,137,138,139,140,143,144,146,147,148
	db	148,150,151,152,153,154,155,157,158,159,161,163,164,166,167,168
	db	168,170,171,172,173,174,175,177,178,179,180,182,183,185,186,187
	db	187,189,190,191,192,193,194,195,196,198,199,200,201,203,204,205
	db	205,205,206,206,207,207,208,208,209,209,210,210,211,211,211,212
	db	212,213,214,214,215,216,216,217,218,219,220,221,222,223,224,225
	db	225,226,227,227,228,229,230,231,232,233,234,235,236,237,237,238
	db	238,238,238,238,238,238,238,238,238,238,239,239,239,240,240,240
	db	240,240,241,241,242,242,242,242,243,243,243,244,244,244,245,245
	db	245,245,246,246,247,247,247,247,248,248,248,249,249,249,250,250
	db	250,250,250,250,250,250,251,251,251,251,251,251,251,251,251,251
	db	251,251,251,251,251,251,251,251,252,252,252,252,252,252,252,252
	db	252,252,252,252,252,252,252,252,253,253,253,253,253,253,253,253
	db	253,253,253,253,253,253,253,253,254,254,254,254,254,254,254,254
	db	254,254,254,254,254,254,254,254,255,255,255,255,255,255,255,255
	db	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
elseif UseTable eq 2
	db	128,128,129,129,130,130,131,131,132,132,133,133,134,134,135,148
	db	148,136,137,137,138,138,139,139,140,140,141,141,142,142,143,168
	db	168,144,145,145,146,146,147,147,148,148,149,149,150,150,151,151
	db	152,152,153,153,154,154,155,155,156,156,157,157,158,158,159,205
	db	205,160,161,161,162,162,163,163,164,164,165,165,166,166,167,212
	db	212,168,169,169,170,170,171,171,172,172,173,173,174,174,175,225
	db	225,176,177,177,178,178,179,179,180,180,181,181,182,182,183,238
	db	238,184,185,185,186,186,187,187,188,188,189,189,190,190,191,240
	db	240,192,193,193,194,194,195,195,196,196,197,197,198,198,199,245
	db	245,200,201,201,202,202,203,203,204,204,205,205,206,206,207,250
	db	250,208,209,209,210,210,211,211,212,212,213,213,214,214,215,251
	db	251,216,217,217,218,218,219,219,220,220,221,221,222,222,223,253
	db	253,224,225,225,226,226,227,227,228,228,229,229,230,230,231,231
	db	232,232,233,233,234,234,235,235,236,236,237,237,238,238,239,254
	db	254,240,241,241,242,242,243,243,244,244,245,245,246,246,247,255
	db	255,248,249,249,250,250,251,251,252,252,253,253,254,254,255,255
endif
	MASM
VxD_Locked_Data_Ends

;*****************************
;*** Sinustabellen-Zugriff ***
;*****************************

VxD_Locked_Code_Seg
	IDEAL
;Wegen der besonderen Zentrierung der Sinustabelle erfolgt die Quadranten-
;umrechnung hier nicht mit dem sonst üblichen NEG (Zweierkomplement),
;sondern mit NOT (Einerkomplement).
;Bei gesetztem Dreieck-Bit wird das Ergebnis der Dreiecksfunktion geliefert.

proc GetCos	;PE: AX: Winkelargument, Vollwinkel=1024
		;PA: AL: 7F-80h-zentriertes Funktionsergebnis
		;VR: AX
	inc	ah
GetSin:
	BTST	[PMotor.Flags],bit 5	;Sinus/Dreieck-Schalter
	jnz	@@dreieck
	push	ebx
	 lea	ebx,[SinTab]
	 test	ah,bit 0
	 jz	@@1	;1. oder 3. Quadrant
	 not	al	;Bit-Komplement bilden
@@1:	 xlat		;Tabellenzugriff
	 test	ah,bit 1;3. oder 4. Quadrant?
	 jz	@@2	;1. oder 2. Quadrant (bleibt >=80h)
	 not	al	;Bit-Komplement bilden (negative Werte <80h)
@@2:	pop	ebx
	ret
@@dreieck:
	test	ah,bit 0	;2./4. Quadrant?
	jz	@@3	;nein
	not	al	;ja, 00-->FF, FF-->00
@@3:	stc		;0-->80, 1-->80, 2-->81, 3-->81, 4-->82,...
	rcr	al,1	;FD-->FE, FE-->FF, FF-->FF (Erster Quadrant)
	test	ah,bit 1	;3./4. Quadrant
	jz	@@4	;nein, positiv lassen
	not	al	;ja, 80-->7F, 81-->7E,... FF-->00
@@4:	ret
endp GetCos

;*************************************************
;*** Portzugriffe auf CMOS und ISEL-MPK3-Karte ***
;*************************************************

IFNDEF UseVTD
proc GetCMOS
;PE: AH=Adresse im CMOS-RAM
;PA: AL=Wert
;VR: AL
	pushf
	 cli
	 xchg	al,ah
	 out	70h,al
	 IO_DELAY
	 xchg	al,ah
	 in	al,71h
	popf
	ret
endp GetCMOS

proc PutCMOS
;PE: AH=Adresse im CMOS-RAM
;    AL=Wert
;VR: -
	pushf
	 cli
	 xchg	al,ah
	 out	70h,al
	 IO_DELAY
	 xchg	al,ah
	 out	71h,al
	popf
	ret
endp PutCMOS
ENDIF

;Ausgabe eines Bytes mit Merkfunktion im "Spiegel"
;PE: AH=Index (0..6), AL=auszugebendes Byte
;    Ist AH>6 wird es auf 6 gesetzt
;PA: -
;VR: ggf. AH
;KO: Ein kurzes CLI sorgt für garantierte Gleichheit des Spiegels
;    mit dem ausgegebenen Wert in allen (INT-)Lebenslagen
proc OutB
	push	edx
	pushfd
	 MIN	ah,6
	 movzx	edx,ah
	 cli
	 mov	[PortMirror+EDX],al
	 add	dx,[PortBase]
	 out	dx,al
	popfd
	pop	edx
	ret
endp

;Einlesen eines Bytes vom Spiegelport oder "echt", wenn AH=6
;PE: AH=Index (0..7) Ist AH>6 wird der Spiegel von PortBase+6 gelesen
;PA: AL=eingelesenes Byte
;VR: AL, ggf. AH, Flags
proc InB
	push	edx
	 MIN	ah,6
	 movzx	edx,ah
	 mov	al,[PortMirror+EDX]
	 jnz	@@e		;wenn AH<> 6 war!
	 add	dx,[PortBase]	;wenn AH=6 direkt einlesen
	 in	al,dx
@@e:	pop	edx
	ret
endp

;*******************************
;*** Direkte Motor-Steuerung ***
;*******************************

;Ausschalten des Stroms durch die Spulen
;PE: EBX: Objektzeiger
;VR: AX
proc motor_aus
	mov	ah,[PMotor.PortA]
	mov	al,128
	call	OutB
	mov	ah,[PMotor.PortB]
	call	OutB			;Spulen (fast) stromlos machen
	mov	[PMotor.Pos],InvPos	;Unbestimmte Position setzen
	ret
endp motor_aus

;Allokieren des Motors; Spulenstrom sicherheitshalber ausschalten,
;jedoch Leistungsendstufen EINschalten
;PE: EBX: Objektzeiger
;VR: AX
proc motor_init
	mov	[PMotor.Flags],bit 0	;in Beschlag nehmen, andere Bits AUS!
	call	motor_aus
	lea	esi,[MotDef]
	lea	edi,[PMotor.MaxSpeed]
	mov	ecx,MotDefElems
	cld
	rep	movsd
	mov	ah,7
	call	InB
	BSET	al,bit 0
	call	OutB		;Spulen einschalten
IFNDEF UseVTD
	mov	ah,0bh
	call	GetCMOS
	BSET	al,bit 6	;CMOS Interrupt 1024Hz aktivieren
	call	PutCMOS
	mov	eax,[IrqHand]
	VxDcall	VPICD_Phys_EOI	;evtl. verklemmten PIC befreien
ENDIF
	ret
endp motor_init

;Freigeben des Motors. Spulenstrom wird ausgeschaltet.
;Sind alle Motoren frei, werden die Leistungsendstufen abgeschaltet
;PE: EBX: Objektzeiger
;VR: AX
proc motor_done
	call	motor_aus
	BRES	[PMotor.Flags],bit 0
	BTST	[MotX.Flags],bit 0	;In Beschlag?
	jnz	@@e
	BTST	[MotY.Flags],bit 0
	jnz	@@e
	BTST	[MotZ.Flags],bit 0
	jnz	@@e
	mov	ah,7
	call	InB
	BRES	al,bit 0	;Alles AUSmachen! (außer Relais)
	call	OutB
@@e:
	ret
endp motor_done

;Motor um vzb. Betrag bewegen, dieser sollte -256..256 nicht überschreiten.
;Dabei wird die Positions-Arbeitszelle mitgeführt, sofern sie nicht
;den Wert "InvPos" enthielt (d.h. die aktuelle Position unbestimmt ist)
;PE: EAX: vzb. Betrag (möglichst -256..256)
;    EBX: Objektzeiger
;VR: AX
proc motor_move
	cmp	[PMotor.Pos],InvPos
	jz	@@1
	add	[PMotor.Pos],eax
@@1:
	add	[PMotor.HWPos],ax
	mov	ax,[PMotor.HWPos]
	push	ax
	 call	GetCos
	 mov	ah,[PMotor.PortA]
	 call	OutB
	pop	ax
	call	GetSin
	mov	ah,[PMotor.PortB]
	call	OutB
	ret
endp motor_move

;Hardware-Endschalter abfragen
;PE: EBX=Objektzeiger
;PA: NZ: Schalter betätigt; AL enthält die gesetzten Bits
;VR: AX
proc TestSchalter
	mov	ah,6
	call	InB
	xor	al,[PMotor.HWEXor]
	and	al,[PMotor.HWEMask]
	ret
endp TestSchalter

;****************************
;*** Periodische Aktionen ***
;****************************

proc intreffahrt
IFDEF LogInt
	MASM
Debug_Out "IntSync "
	IDEAL
ENDIF
	call	TestSchalter		;Hardware-Endschalter abfragen
	jnz	@@1
	xor	eax,eax
	BTS	[PMotor.Flags],3	;Merkbit "Nicht auf Endschalter"
	jnc	@@3			;im Wendepunkt 1x aussetzen

	BT	[PMotor.Flags],7
@@5:	mov	eax,[PMotor.RefSpeed]
@@3:	call	MoveBySpeedDir
	ret
@@1:
	BTST	[PMotor.Flags],bit 3	;Gerade draufgekommen?
	jnz	@@4
	BT	[PMotor.Flags],7	;nein, etwas zurückfahren!
	cmc
	jmp	@@5
@@4:	;neu: kein "Einrütteln" in exakte Position, sondern
	;Aufaddieren der Hardware-Position zum Referenzpunkt
	mov	ax,[PMotor.HWPos]	;10-Bit-Zahl holen
	shl	eax,22
	sar	eax,22	;Einfachste Möglichkeit der Vorzeichenerweiterung
	add	eax,[PMotor.Refpoint]
SetRefposHere:
	mov	[PMotor.Pos],eax	;Das ist nun die Position
	BRES	[PMotor.Flags],0Eh	;Bit1+Bit2+Bit3
	EndInterrupts
	call	InformWindows
	ret
endp intreffahrt

proc intmove
	BTST	[PMotor.Flags],bit 1
	jz	@@e
	BTST	[PMotor.Flags],bit 2
	jz	@@m
	call	intreffahrt
	jmp	@@e
@@m:	call	intmoveproc
@@e:	ret
endp

proc intproc		;Das Innere des Timer-Interrupts
IFDEF UseVTD
	mov	eax,1
	mov	esi,OFFSET32 intproc
	MASM
	VMMcall	Set_Global_Time_Out
	IDEAL
ENDIF
	lea	ebx,[MotX]
	call	intmove
	lea	ebx,[MotY]
	call	intmove
	lea	ebx,[MotZ]
	call	intmove
	inc	[upcounter]
	ret
endp intproc

;*****************************************
;*** Aktions-Anstöße für Sync und Move ***
;*****************************************

;PE: EAX=vzb. Differenz
proc StartSegment
	BRES	[PMotor.Flags],bit 7	;Positiv annehmen
	sub	eax,[PMotor.Pos]	;Differenz
	jge	@@1
	neg	eax			;Betrag bilden
	BSET	[PMotor.Flags],bit 7	;und Richtung merken
@@1:	push	eax
	 xor	eax,eax
	 mov	[PMotor.Speed],eax	;Geschwindigkeit nullsetzen
	 mov	[PMotor.WegLo],eax
	 mov	[PMotor.WegHi],eax
	pop	eax
	mov	[PMotor.WegMid],eax	;und Weg entsprechend setzen
	BeginInterrupts
	BSET	[PMotor.Flags],Bit 1	;Und LOS!
@@e:	ret
endp

proc StartReffahrt
	cmp	eax,InvPos		;INVPOS: Setzen Referenz HIER!
	jz	@@a			;(zum Debuggen ohne Hardware)
	mov	[PMotor.Refpoint],eax	;wird dann eingetragen
	mov	[PMotor.Speed],0
	BRES	[PMotor.Flags],bit 3	;Annahme: Motor steht AUF Endschalter
	BeginInterrupts
	BSET	[PMotor.Flags],bit 1 + bit 2	;ISR werkeln lassen
@@e:	ret
@@a:	xor	eax,eax
	jmp	SetRefposHere		;Debugging-Referenzpunkt
endp

;*********************************************
;*** Testfunktionen mit Fehlercoderückgabe ***
;*********************************************

;Testet, ob Motor belegt, und liefert, wenn ja, CY=1 und AL=Code zurück
proc TestFrei
	BTST	[PMotor.Flags],bit 0	;Belegt?
	jz	@@e		;nein, okay
	mov	al,ME_AlreadyAssigned
	stc
@@e:	ret
endp

;Testet, ob Motor belegt, und liefert, wenn nicht, CY=1 und AL=Code zurück
proc TestBelegt
	BTST	[PMotor.Flags],bit 0	;Belegt?
	jnz	@@e		;ja, okay
	mov	al,ME_NotAssigned
	stc
@@e:	ret
endp

;Testet, ob Motor in Bewegung ist, und liefert, wenn ja, CY=1 und AL=Code zurück
proc TestStill
	BTST	[PMotor.Flags],bit 1	;In Bewegung?
	jz	@@e		;nein, okay
	mov	al,ME_InMove
	stc
@@e:	ret
endp

;Testet, ob Motor gültige Position hat, d.h. ob eine Referenzfahrt
;ausgeführt wurde
proc TestReferenz
	cmp	[PMotor.Pos],InvPos	;Ungültig?
	clc
	jnz	@@e		;Gültig!
	mov	al,ME_NoRef
	stc
@@e:	ret
endp

proc TestInBounds
	cmp	eax,[PMotor.LeftBound]
	jl	@@e
	cmp	eax,[PMotor.RightBound]
	jg	@@e
	clc
	ret
@@e:	mov	al,ME_SoftEnd
	stc
	ret
endp

;******************************************
;*** Rückruf-Funktionen für PostMessage ***
;******************************************

proc PostEventProc
	mov	ebx,edx		;geordnete Verhältnisse
	cmp	[PMotor.CallbackAddr],0
	jz	@@e
	Debug_Halt
	MASM
	Push_Client_State
	VMMCall	Begin_Nest_Exec
	IDEAL
	mov	ax,[word LOW PMotor.CallbackWnd]	;Window
	MASM
	VMMCall	Simulate_Push
	IDEAL
	mov	ax,[word HIGH PMotor.CallbackWnd]	;Message
	MASM
	VMMCall	Simulate_Push
	xor	eax,eax		;wParam=0
	VMMCall	Simulate_Push
	IDEAL
	mov	eax,[PMotor.Pos]	;lParam=Position
	call	HerUeber	;Wandeln!
	push	eax
	shr	eax,16		;erst High-Teil (Umm!)
	MASM
	VMMCall	Simulate_Push
	IDEAL
	pop	eax		;dann Low-Teil!
	MASM
	VMMCall	Simulate_Push
	IDEAL
	movzx	edx,[word LOW PMotor.CallbackAddr]
	mov	cx,[word HIGH PMotor.CallbackAddr]
	MASM
	VMMCall	Simulate_Far_Call
	VMMCall	Resume_Exec
	VMMCall	End_Nest_Exec
	Pop_Client_State
	IDEAL
@@e:
	ret
endp

proc InformWindows c
uses ebx,ecx,edx
	mov	edx,ebx
	mov	eax,High_Pri_Device_Boost
	MASM
	VMMCall	Get_Sys_VM_Handle
	mov	ecx,PEF_Wait_For_STI or PEF_Wait_Not_Crit or PEF_Always_Sched
	lea	esi,[PostEventProc]
	xor	edi,edi		;Timeout sicherheitshalber auf Null
	VMMCall	Call_Priority_VM_Event
	IDEAL
	ret
endp

;***********************************************
;*** Berechnung des Geschwindigkeitsverlaufs ***
;***********************************************

;PE: EAX=vzl. Geschwindigkeit in Nanoschritten
;    EBX=Objektzeiger
;bei MoveBySpeedDir: CY=1: Rückwärts
;PA: - Die Momentangeschwindigkeit wird auf EAX gesetzt
;    - Der Rest-Weg wird entsprechend "gekürzt"
;    - Entsprechend der sich daraus ergebenden Nanoschritt-Differenz
;      werden Mikroschritte in die in Flags angegebene Richtung ausgeführt
proc MoveBySpeed
	BT	[PMotor.Flags],7	;Richtungs-Bit nach CY
MoveBySpeedDir:
	pushfd
	 mov	[PMotor.Speed],eax
	 push	edx
	  mov	edx,[PMotor.WegMid]
	  sub	[PMotor.WegLo],eax
	  sbb	[PMotor.WegHi],0	;Restweg verringert sich
	  sub	edx,[PMotor.WegMid]	;Differenz!
	  mov	eax,edx
	 pop	edx
	popfd
	jnc	@@p
	neg	eax
@@p:	call	motor_move
	ret
endp

;Funktion "Bremsweg berechnen"
;PE: EAX: Momentane (angenommene) Geschwindigkeit, vzl., in Nanoschritten
;    EBX: Zeiger auf Bewegungsstruktur (mit vzl. MaxAccel)
;PA: EDX:EAX: Bremsweg (vzl.)
;VR: EDX,EAX
IFDEF UseLoop
proc Bremsweg c
uses esi
;for(EDX:ESI=0, EAX=Speed; EAX>0; EAX-=MaxAccel) EDX:ESI+=ECX;
	xor	edx,edx
	xor	esi,esi
@@l:	add	ecx,eax
	adc	edx,0
	sub	eax,[PMotor.MaxAccel]
	ja	@@l
	mov	eax,esi
	ret
endp
ELSE
;Variante 2: schleifenlos
proc Bremsweg c
uses esi,edi
	push	eax	;A1
	xor	edx,edx
	div	[PMotor.MaxAccel]
	inc	eax	;N ermitteln = A1/d +1
	push	eax
	mov	edx,eax
	dec	edx
	mul	edx	;N(N+1)
	shr	eax,1	;N(N+1)/2
	mul	[PMotor.MaxAccel]	;EDX:EAX=N(N-1)d/2
	mov	esi,edx
	mov	edi,eax	;nach ESI:EDI
	pop	eax	;N
	pop	edx	;A1
	mul	edx
	sub	eax,edi
	sbb	edx,esi
	ret
endp
ENDIF

;Funktion "Geschwindigkeit berechnen"
;Anhand des noch zurückzulegenden Restwegs (PMotor.Weg) und
;weiterer Parameter wird ein neuer - möglichst maximaler -
;Geschwindigkeitswert ermittelt
;PE: EBX: Zeiger auf Bewegungsstruktur
;PA: EAX: Neue Geschwindigkeit
proc CalcNewSpeed c
uses ecx,edx,esi
	mov	eax,[PMotor.Speed]
	call	Bremsweg		;Daraus Bremsweg ermitteln
	stc
	sbb	eax,[PMotor.WegLo]	;(Bremsweg-=Restweg+1)
	sbb	edx,[PMotor.WegHi]	;Zu lang?
	jnc	@@ReduceSpeed		;wenn Bremsweg>Restweg
	mov	eax,[PMotor.Speed]
	cmp	[PMotor.MaxSpeed],eax	;Zu groß, wenn Höchstgeschwindigkeit
	jc	@@ReduceSpeed		;verringert wurde
	add	eax,[PMotor.MaxAccel]	;Beschleunigen (versuchen)
	MIN	eax,[PMotor.MaxSpeed]	;Höchstgeschwindigkeit!
	mov	esi,eax			;Merken
	cmp	[PMotor.WegHi],0
	jnz	@@1
	MIN	esi,[PMotor.WegLo]	;Extremfall: Sehr kurzer Weg<Accel
@@1:	call	Bremsweg
	stc
	sbb	eax,[PMotor.WegLo]	;Bremsweg wäre zu lang?
	sbb	edx,[PMotor.WegHi]	
	jnc	@@NoAccel
	mov	eax,esi		;Beschleunigungs- oder Fahrphase
	jmp	@@e
@@NoAccel:			;(Kurze) Haltephase beim Abbremsen
	mov	eax,[PMotor.Speed]
	jmp	@@e
@@ReduceSpeed:
	mov	eax,[PMotor.Speed]
	sub	eax,[PMotor.MaxAccel]
	ja	@@e		;Stets >0
	mov	eax,[PMotor.WegLo]
@@e:
	ret
endp CalcNewSpeed

proc intmoveproc
IFDEF LogInt
	MASM
Debug_Out "IntMove "
	IDEAL
ENDIF
	mov	eax,[PMotor.WegLo]
	or	eax,[PMotor.WegHi]	;Nichts mehr tun?
	jz	@@1
	BTST	[PMotor.Flags],bit 4	;HWE-Schaltertest?
	jz	@@2
	call	TestSchalter		;Hardware-Endschalter abfragen
	jnz	@@3
@@2:	call	CalcNewSpeed
	call	MoveBySpeed
	ret

@@3:	xor	eax,eax
	mov	[PMotor.WegLo],eax
	mov	[PMotor.WegHi],eax
	mov	[PMotor.Pos],InvPos
@@1:
	mov	[PMotor.Speed],eax
	BRES	[PMotor.Flags],bit 1	;Bewegungssegment ENDE
	EndInterrupts
	call	InformWindows
	ret
endp

;************************************************************
;*** Übersetzung zwischen System- und Anwenderkoordinaten ***
;************************************************************

;Integer-Division mit korrekter Rundung
;PE: EDX:EAX=Dividend, ECX=Divisor
proc IdivRound
	push	ebx edx		;EBX und Dividend retten
	mov	ebx,eax
	xor	edx,ecx		;Vorzeichen des Ergebnisses
	mov	eax,ecx		;Divisor
	jns	@@1		;gleiches Vorzeichen: Divisor belassen
	neg	eax		;ungleiches Vorzeichen: Vorzeichentausch
@@1:	jns	@@2		;Positiv: OK
	inc	eax		;Negativ: Um 1 vergrößern, um den
 ;Abrundungseffekt von SAR bei negativen Zahlen zu kompensieren (1->0, -1->-1)
@@2:	sar	eax,1		;Divisor vorzeichenrichtig halbieren
	cdq			;halben Divisor auf EDX:EAX erweitern
	add	eax,ebx		;altes EAX addieren
	pop	ebx		;altes EDX
	adc	edx,ebx		;Addition ist kommutativ
	pop	ebx
	idiv	ecx
	ret
endp

;Dieses Makro arbeitet wie der idiv-Befehl
macro IDIVR arg
	push	ecx
	 mov	ecx,arg
	 call	IdivRound
	pop	ecx
endm

;Hin-Übersetzung: Schritt-Postition:=User-Position*Umrech2/Umrech1
;PE: EAX=Anwender-Position
;    EBX=Objektzeiger
;PA: EAX=Schrittmotor-Position, CY=0
;VR: EAX, Flags
;N:  Die "ungültige Position" INVPOS wird niemals umgerechnet
proc HinUeber c
uses edx
	cmp	eax,InvPos
	jz	@@e
	imul	[PMotor.Umrech2]
	IDIVR	[PMotor.Umrech1]
@@e:	clc
	ret
endp

HerUeberA:		;EAX=vzl.Beschleunigung in Nanoschritt/Tick pro Tick
	push	edx
	mul	[Frequency]
	pop	edx
HerUeberV:		;EAX=vzl.Geschwindigkeit in Nanoschritt pro Tick
	push	edx
	mul	[Frequency]
	shrd	eax,edx,16
	pop	edx
;Her-Übersetzung: User-Postition:=Schritt-Position*Umrech1/Umrech2
;PE: EAX=Schrittmotor-Position
;    EBX=Objektzeiger
;PA: EAX=Anwender-Position, CY=0
;VR: EAX, Flags
;N:  Die "ungültige Position" INVPOS wird niemals umgerechnet
proc HerUeber c
uses edx
	cmp	eax,InvPos
	jz	@@e
	imul	[PMotor.Umrech1]
	IDIVR	[PMotor.Umrech2]
@@e:	clc
	ret
endp

HinUeberV:
	mov	cl,1
;Hinüber-Übersetzung mit nachfolgender Betragsbildung.
;Ist das Ergebnis Null, wird 1 gesetzt
;PE: CL=1 (v) oder 2 (a)
;VR: CL, EAX
proc HinPositiv c
uses edx
	call	HinUeber
	mov	edx,eax
	sar	edx,16
	shl	eax,16		;EDX:EAX:=EAX*65536, vzb.
@@l:	IDIVR	[Frequency]
	cdq			;Rest verwerfen
	dec	cl
	jnz	@@l
	RABS	eax		;Betrag bilden
	jnz	@@2		;Null? nein
	inc	eax		;ja: EAX:=1
@@2:	ret
endp

;***************************
;*** Neue API-Funktionen ***
;***************************

proc SMNop	;"NOP"-Kommando
	ret
endp

;*************************************
proc SMFree	;Motorspulen stromfrei schalten
	call	TestBelegt
	jc	@@e
	call	TestStill
	jc	@@e
	push	eax
	 call	motor_aus
	pop	eax
@@e:
	ret
endp

;*************************************
proc SMSync	;Referenzfahrt
	call	TestBelegt
	jc	@@e
	call	TestStill
	jc	@@e
	cmp	eax,InvPos
	jz	@@1		;"Debug-Position"
	call	TestInBounds
	jc	@@e
	call	HinUeber
	xchg	edx,eax
	BRES	[PMotor.Flags],bit 7
	or	eax,eax
	jns	@@p
	BSET	[PMotor.Flags],bit 7
	neg	eax
@@p:	jz	@@q		;Keine Geschwindikeit setzen
	cmp	eax,1		;Betrag war 1?
	jz	@@q		;dann auch keine Geschwindigkeit setzen
	call	HinUeberV
	mov	[PMotor.RefSpeed],eax
@@q:	xchg	edx,eax
@@1:	call    StartReffahrt
@@e:
	ret
endp

;*************************************
proc SMMoveAbs	;Absolutbewegung
	call	TestBelegt
	jc	@@e
	call	TestStill
	jc	@@e
	call	TestReferenz
	jc	@@e
	call	TestInBounds
	jc	@@e
	call	HinUeber
	call	StartSegment
@@e:
	ret
endp

;*************************************
proc SMMoveRel	;Relativbewegung
	call	TestBelegt
	jc	@@e
	call	TestStill
	jc	@@e
	call	TestReferenz
	jc	@@e
	call	TestInBounds
	jc	@@e
	call	HinUeber
	add	eax,[PMotor.Pos]
	call	StartSegment
@@e:
	ret
endp

;*************************************
proc SMAssign	;Belegung
	call	TestFrei
	jc	@@e
	call	motor_init
@@e:
	ret
endp

;*************************************
proc SMUnAssign	;Freigabe
	call	SMFree
	jc	@@e
	call	motor_done
	mov	[PMotor.CallbackAddr],0	;Nie mehr rückrufen!
@@e:
	ret
endp

;*************************************
proc SMSetSA	;Geschwindigkeit und Beschleunigung festlegen
	call	TestBelegt
	jc	@@e
	call	HinUeberV	;Geschwindigkeit hinrechnen
	xchg	[PMotor.MaxSpeed],eax
	call	HerUeberV	;Geschwindigkeit rückrechnen
	RABS	eax
	xchg	eax,edx
	mov	cl,2
	call	HinPositiv	;Beschleunigung hinrechnen
	xchg	[PMotor.MaxAccel],eax
	call	HerUeberA	;Beschleunigung rückrechnen
	RABS	eax
	xchg	edx,eax
IFDEF UseLoop
	push	edx eax
	mov	eax,[PMotor.MaxSpeed]
	xor	edx,edx
	div	[Frequency]
	MAX	[PMotor.MaxAccel],eax	;Beschleunigung mindestens,
;daß max. 1 Sekunde Beschleunigungsphase ist
	pop	eax edx
ENDIF
@@e:
	ret
endp

;*************************************
proc SMGetSA	;Geschwindigkeit und Beschleunigung erfragen
	call	TestBelegt
	jc	@@e
	mov	eax,[PMotor.MaxAccel]
	call	HerUeberA
	RABS	eax
	xchg	edx,eax
	mov	eax,[PMotor.MaxSpeed]
	call	HerUeberV
	RABS	eax
@@e:
	ret
endp

;*************************************
proc SMSetCallback	;Rückrufadresse (@PostMessage) sowie
			;Fenster und WM-Nachricht festlegen
;Diese wird ausschließlich in die System-VM gerufen!
;Window=angegeben, Message=angegeben, wParam=0 (Ursache), lParam=Position
	call	TestBelegt
	jc	@@e
	xchg	[PMotor.CallbackAddr],eax
	xchg	[PMotor.CallbackWnd],edx
@@e:
	ret
endp

;*************************************
proc SMGetCallback	;Obiges zurücklesen
	call	TestBelegt
	jc	@@e
	mov	eax,[PMotor.CallbackAddr]
	mov	edx,[PMotor.CallbackWnd]
@@e:
	ret
endp

;*************************************
proc SMGetPosition	;liefert Position und Geschwindigkeit
	call	TestBelegt
	jc	@@e
	pushf
	 cli		;Ein zusammenhängendes Paar erwischen!
	 mov	eax,[PMotor.Pos]
	 mov	edx,[PMotor.Speed]
	popf
	call	HerUeber
	xchg	eax,edx
	call	HerUeber
	xchg	edx,eax
@@e:
	ret
endp

;*************************************
proc SMSetBounds
	call	TestBelegt
	jc	@@e
	xchg	[PMotor.LeftBound],eax
	xchg	[PMotor.RightBound],edx
@@e:
	ret
endp

;*************************************
proc SMGetBounds
	call	TestBelegt
	jc	@@e
	mov	eax,[PMotor.LeftBound]
	mov	edx,[PMotor.RightBound]
@@e:
	ret
endp

;*************************************
proc SMStop
	call	TestBelegt
	jc	@@e
	BRES	[PMotor.Flags],bit 2	;ISR-Arbeit radikal stoppen
	btr	[PMotor.Flags],1
	jnc	@@e
	EndInterrupts
	call	InformWindows
	clc
@@e:
	ret
endp

;*************************************
proc SMSetGear	;Übersetzungsverhältnis setzen
	call	TestBelegt
	jc	@@e
	or	eax,eax		;Nur setzen wenn nicht Null!
	jz	@@1
	xchg	[PMotor.Umrech1],eax
	jmp	@@1a
@@1:	mov	eax,[PMotor.Umrech1]
@@1a:	or	edx,edx		;Nur setzen wenn nicht Null!
	jz	@@2
	xchg	[PMotor.Umrech2],edx
	jmp	@@2a
@@2:	mov	edx,[PMotor.Umrech2]	
@@2a:
@@e:
	ret
endp

;*************************************
proc SMGetGear	;Übersetzungsverhältnis rücklesen
	call	TestBelegt
	jc	@@e
	mov	eax,[PMotor.Umrech1]
	mov	edx,[PMotor.Umrech2]
@@e:
	ret
endp

;*************************************
proc SMSetHWE	;Hardware-Endschalter-Maske setzen
;AL=Maske, AH=XOR-Byte, Bit 16(EAX)=Schalter, ob bei Geradenbewegung
;der HWE-Schalter geprüft werden soll, DL=Offset PortA, DH=Offset PortB
;Bit17(EAX)=Schalter Dreieck statt Sinus
	call	TestBelegt
	jc	@@e
	or	ax,ax			;Maske setzen?
	jz	@@1			;nur rücklesen wenn Null
	xchg	[word PMotor.HWEMask],ax
	jmp	@@1a
@@1:	mov	ax,[word PMotor.HWEMask]
@@1a:
	or	dx,dx			;Ports setzen?
	jz	@@2			;nur rücklesen wenn Null
	xchg	[word PMotor.PortA],dx
	jmp	@@2a
@@2:	mov	dx,[word PMotor.PortA]
@@2a:
	BRES	[PMotor.Flags],bit 4	;Prüfung ausschalten
	BT	eax,16
	jnc	@@3
	BSET	[PMotor.Flags],bit 4	;Prüfung einschalten
@@3:
	BRES	[PMotor.Flags],bit 5	;auf Sinus schalten
	BT	eax,17
	jnc	@@e
	BSET	[PMotor.Flags],bit 5	;auf Dreieck schalten
@@e:
	ret
endp
	MASM
VxD_Locked_Code_Ends

;*************************************
;*** Neue API - Funktionsverteiler ***
;*************************************
VxD_Locked_Data_Seg
	IDEAL
;Unterprogramm-Verteilertabelle
;Diese UP's werden mit EBX=Zeiger auf Motorstruktur angesprungen
upvt	dd	OFFSET32 SMNop		;"NOP"-Kommando
	dd	OFFSET32 SMFree		;Motorspulen stromfrei schalten
	dd	OFFSET32 SMSync		;Referenzfahrt
	dd	OFFSET32 SMMoveAbs	;Absolutbewegung
	dd	OFFSET32 SMMoveRel	;Relativbewegung
	dd	OFFSET32 SMAssign	;Belegung
	dd	OFFSET32 SMUnAssign	;Freigabe
	dd	OFFSET32 SMSetSA	;Geschw + Beschl. festlegen
	dd	OFFSET32 SMGetSA	;Geschw + Beschl. rücklesen
	dd	OFFSET32 SMSetCallback;
	dd	OFFSET32 SMGetCallback;
	dd	OFFSET32 SMGetPosition	;Momentane Position ermitteln (nach EDX)
	dd	OFFSET32 SMSetBounds
	dd	OFFSET32 SMGetBounds
	dd	OFFSET32 SMStop		;Not-Stopp, jedoch Spulen unter Strom
	dd	OFFSET32 SMSetGear	;Übersetzungsverhältnis setzen
	dd	OFFSET32 SMGetGear	;Übersetzungsverhältnis rücklesen
	dd	OFFSET32 SMSetHWE	;Hardware-Endschalter-Maske setzen
	MASM
VxD_Locked_Data_Ends

VxD_Locked_Code_Seg

BeginProc MPK3D_Api
	pushad
	mov	ax,[ebp.Client_AX]	;AX laden
	cmp	ah,SM_LastFunc
	ja	@@NoFunc
	dec	al
	cmp	al,3
	jnc	@@NoUnit
Debug_Out "ApiCall Fkt=#AH"
	movzx	ecx,ah
	mov	ah,size TMotor
	mul	ah
	movzx	ebx,ax
	add	ebx,OFFSET32 MotX
	mov	eax,[ebp.Client_EDX]	;1. Longint-Parameter
	mov	edx,[ebp.Client_ECX]	;2. LongInt-Parameter
	call	[upvt+ecx*4]
	jc	@@err			;Bei Carry AL zurückgeben
	mov	[ebp.Client_ECX],edx
	mov	[ebp.Client_EDX],eax
	xor	al,al			;sonst 0 = Kein Fehler
	BRES	[ebp.Client_Flags],CF_Mask	;Carry setzen
	jmp	@@NoErr
@@NoUnit:
	mov	al,ME_NoDriver
@@NoFunc:
	mov	al,ME_WrongFunction
@@Err:
	BSET	[ebp.Client_Flags],CF_Mask	;Carry setzen
@@NoErr:
	mov	[ebp.Client_AL],al
	popad
	ret
EndProc MPK3D_Api
	IDEAL

;********************************
;*** Interruptserviceroutinen ***
;********************************
IFNDEF UseVTD
;Interruptserviceroutine für den CMOS-Tick mit nominell 1024Hz
;PE: EAX=IRQ-Griff
;    EBX=VM-Griff
	align	4
proc CmosIsr
	sti
	pushad
	mov	ah,0ch
	call	GetCMOS
	BTST	al,bit 6	;Ursache=Echtzeituhr?
	jz	@@chain		;nein->nicht bedienen!
	call	intproc
	popad
	MASM
	VxDcall	VPICD_Phys_EOI
	IDEAL
	clc			;ISR behandelte IRQ
	ret
@@chain:
	Debug_Halt
	popad
	stc			;weiter verketten!
	ret
endp CmosIsr
ENDIF

;Trapserviceroutine für Portzugriffe auf die geschützten MPK3-Adressen
;PE: EDX=Portadresse, ECX=Zugriffsart, EBX=VM-Griff, EAX=EA-Daten
	align	4
proc IOHandler
	MASM
	Dispatch_Byte_IO Fall_Through,@@o
	push	edx
	 sub	dx,[PortBase]
	 push	eax
	  mov	ah,dl
	  call	InB		;Spiegelbyte oder (AH=6) Port liefern
	  mov	dl,al
	 pop	eax
	 mov	al,dl
	pop	edx
	ret
@@o:
	push	edx eax
	 sub	dx,[PortBase]
	 mov	ah,dl
	 call	OutB		;Port schreiben, Spiegelbyte aktualisieren
	pop	eax edx
	ret
	IDEAL
endp IOHandler


;*********************
;*** Exit-Funktion ***
;*********************
proc MPK3D_Exit
	MASM
Debug_Out "MPK3D_Exit"
	IDEAL
	mov	ax,600h
	call	OutB		;Leistungsendstufen und Relais AUS
IFNDEF UseVTD
	mov	ah,0bh
	call	GetCMOS
	BRES	al,bit 6	;CMOS Interrupt 1024Hz abschalten
	call	PutCMOS
	mov	eax,[IrqHand]
	VxDcall	VPICD_Physically_Mask	;IRQs aus
	VxDcall	VPICD_Force_Default_Behavior
ENDIF
	ret
endp

	MASM
VxD_Locked_Code_Ends

;******************************
;*** Installations-Funktion ***
;******************************
VxD_IData_Seg
IFNDEF UseVTD
IrqDesc	VPICD_Irq_Descriptor <8,VPICD_Opt_Can_Share,OFFSET32 CmosIsr>
ENDIF
Port$:	dz	'MPK3DPort'
VxD_IData_Ends

VxD_ICode_Seg

BeginProc MPK3D_Init
;* IRQ8 freischalten
;* Portadresse aus SYSTEM.INI holen
;* Ports trappen
;* Motoren zurücksetzen
Debug_Out "MPK3D_Init"
IFNDEF UseVTD
	lea	edi,IrqDesc
	VxDcall	VPICD_Virtualize_IRQ
	jc	@@e
	mov	[IrqHand],eax
	VxDcall	VPICD_Physically_Unmask
	mov	ah,0bh
	call	GetCMOS
	BSET	al,bit 6	;CMOS Interrupt 1024Hz laufenlassen
	call	PutCMOS
;	mov	[Frequency],1024
ENDIF
	movzx	eax,[PortBase]	;Default
	xor	esi,esi		;Sektion [386Enh]
	lea	edi,Port$	;Schlüssel MPK3DPort
	VMMCall	Get_Profile_Hex_Int	;Aus SYSTEM.INI ein Wert lesen
	mov	[PortBase],ax
	movzx	edx,ax
	mov	esi,OFFSET32 IOHandler
	mov	ecx,8		;8 Ports überwachen
@@l:	VMMcall	Install_IO_Handler	;die Ports der Karte "verfeinern"
	inc	edx
	loopd	@@l
	IDEAL
	mov	ax,600h
	call	OutB		;Endstufen und Relais AUS
	lea	ebx,[MotX]
	call	motor_aus
	lea	ebx,[MotY]
	call	motor_aus
	lea	ebx,[MotZ]
	call	motor_aus
	clc
	MASM
@@e:
	ret
EndProc MPK3D_Init

VxD_ICode_Ends

	END
Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded