Source file: /~heha/messtech/kreuzt.zip/LASER-MP/UMSTEP.ASM

;Schrittmotor-Steuerung für M20-Karte
;LogInt	equ	1		;Jeden Interrupt loggen
	%MACS
	%NOINCL
	%TITLE "Mikroschritt-Motortreiber-VxD"
	%SUBTTL "Programmkopf: Vereinbarungen usw."
	include tvmm2.inc	;Generelles
	include	debug2.inc	;wegen Debugging
	include	vpicd.inc	;Interrupt-Controller
	IDEAL
UMSTEP_Device_ID	equ	3C0Eh	;von Microsoft bestätigt:
;>You have been assigned a device ID of 3C0Eh for your UMSTEP.386 virtual
;>device.  This number is uniquely assigned to this device.
UMSTEP_Major_Ver	equ	1
UMSTEP_Minor_Ver	equ	10

;=============================
;Diese Programmversion arbeitet nach außen voll mit 16-bit-Registern,
;da ein gleichartiges TSR-Programm für 286er denkbar ist (die geringere
;Rechenleistung ist nicht so sehr das Problem).
ifdef DEBUG
	DISPLAY	!Assembliere Debugversion!
endif

Declare_Virtual_Device  UMSTEP, UMSTEP_Major_Ver, UMSTEP_Minor_Ver,\
			UMSTEP_Ctl, UMSTEP_Device_ID,\
			Undefined_Init_Order, UMSTEP_API, UMSTEP_API

;*********************************************************
;*** Obligatorischer VxD-Botschafts-Funktionsverteiler ***
;*********************************************************
VxD_Locked_Code_Seg
BeginProc UMSTEP_Ctl
	Control_Dispatch Device_Init, UMSTEP_Init
	Control_Dispatch System_Exit, UMSTEP_Exit
	Control_Dispatch VM_Not_Executeable, UMSTEP_VMDies
	Control_Dispatch 1bh, UMSTEP_Dynamic_Init
	Control_Dispatch 1ch, UMSTEP_Dynamic_Exit
	clc
	ret
EndProc UMSTEP_Ctl
VxD_Locked_Code_ends

;********************************************************
;*** Residenter Datenbereich und Konstantendefinition ***
;********************************************************

;Folgende Kommandos gibt es:
SM_GetVer	equ	0	;Versionsnummer holen
SM_SetMotor	equ	1	;Motor hinzufügen, Parameter setzen/ändern
				;(AL=ForceAlloc-Flag, ES:BX=Mem)
SM_GetMotor	equ	2	;Motorparameter holen (AL=Handle, ES:BX=Mem)
SM_RemoveMotor	equ	3	;Motor entfernen (AL=Handle)
SM_SetIntFreq	equ	4	;Interruptfrequenz setzen (BX=Frequenz)
SM_GetIntFreq	equ	5	;Interruptfrequenz holen (BX=Frequenz)
SM_Sync		equ	6	;Referenzfahrt
SM_MoveAbs	equ	7	;Absolutbewegung nach CX:DX
SM_MoveRel	equ	8	;Relativbewegung um CX:DX
SM_Stop		equ	9	;Soforthalt
SM_Free		equ	10	;Motorspulen stromfrei schalten
SM_GetPosition	equ	11	;Position (EDX=CX:DX) und Geschwindigkeit
				;(ESI=DI:SI) erfragen, BX=Frequenz)
SM_LastFunc	equ	11
;{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_InvalHandle	equ	50;
ME_AlreadyAssigned equ	51;
ME_OutOfHandles	equ	52;
ME_OutOfMemory	equ	53;
ME_InvalPointer	equ	54;
ME_InvalPort	equ	55;
ME_InternErr	equ	56;

NomFreq		equ	1024	;Nominale Frequenz

MC_Observe	=0	;Beachte Endschalter bei Normalbewegung
MC_SyncedOnFree	=1	;Synchronisation beim FREE-Kommando halten
MC_AlwaysSynced	=2	;Annahme: Immer synchronisiert!
MC_SyncRequired	=3	;Synchronisation vor MOVE ERFORDERLICH
MC_Signed	=4	;Vorzeichenbehafteter Mikroschritt-DAC
MC_Table	=5	;Tabelle statt Funktion (Dreieck<->Sinus)
MC_Trapeze	=6	;Trapezförmiger Strom (Trapez<->"Kurve")

MF_InMove	=0	;Motor in Bewegung
MF_InSync	=1	;Referenzfahrt erfolgt gerade
MF_Synced	=2	;Referenzfahrt war erfolgreich
MF_HWECheck	=4	;Endschalter angestoßen: Referenzfahrt erforderlich!
MF_NegDir	=7	;Negative Bewegungsrichtung

struc TMotor
;Aufteilung nach konstantem und variablem Teil
;Der konstante Teil kann via GetMotor() abgefragt und SetMotor() gesetzt werden
 Handle		db	?	;Motor-Handle (0=NEUER MOTOR)
 CFlags		db	0	;Konstante Motor-Flags
 HWEMask	db	1	;Hardware-Endschalter-Maske
 HWEXor		db	?	;Hardware-Endschalter-XOR-Bits
 PortHWE	dw	?	;Portadresse HWE
 union
  Ports		dd	?
  struc
   PortA	dw	?	;Portadresse Spule A (Vergleichskriterium!)
   PortB	dw	?	;Portadresse Spule B (Vergleichskriterium!)
  ends
 ends
 CallbackAddr	dd	0	;16:16-Rückrufadresse
 CallbackDS	dw	?	;Datensegment für Callback
 CallbackUser	dd	?	;32-bit-Anwenderdaten
 MaxSpeed	dd	48*65536;Maximalgeschwindigkeit, vzl.
 RefSpeed	dd	12*65536;Referenz-Geschwindigkeit, vzb.!
 MaxAccel	dd	48	;Maximalbeschleunigung, vzl.!
 Refpoint	dd	0	;Referenzpunkt nach Sync
 LeftBound	dd	-MaxLong;Software-Anschläge
 RightBound	dd	MaxLong
;Der Callback wird gerufen mit
;EDX=CX:DX=Anwender-Daten, AL=Handle, AH=Flags, ESI=DI:SI=Position

;Der folgende variable Teil ist für die VxD-interne Benutzung vorgesehen
 HWPos		dw	0	;"Hardware-Position" als Index in Sinustabelle
 union
  Weg		df	0	;6-Byte-Wert als Restwegspeicher
  struc
   WegLo	dd	?
   WegHi	dw	?
  ends
  struc
   WegLoW	dw	?
   WegMid	dd	?
  ends
 ends
 Pos		dd	0	;Aktuelle Position
 Speed		dd	0	;Momentangeschwindigkeit
 CallbackVM	dd	?	;Virtuelle Maschine
 Flags		dd	?	;Variable Motor-Flags
ends TMotor
;Diese Motor-Struktur wird in einer asynchronen Liste verwaltet.
;Das erlaubt die Ansteuerung von max. 31 Motoren gleichzeitig

PMotor	equ	(TMotor ebx)	;Objektzeiger ist immer EBX
TMotor_ConstLongs equ	11
TMotor_VarLongs	equ	6

	%SUBTTL "Handles und Tabellen"
VxD_Locked_Data_Seg
ListHand	dd	0	;Listen-Handle
IrqHand		dd	0	;IRQ-Griff
Frequency	dw	NomFreq	;für Zeitmessungen usw.
upcounter	dd	?

;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:
	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
KurvTab:
	db	128,130,131,133,134,136,138,139,141,142,144,145,147,148,150,151
	db	152,154,155,157,158,159,161,162,163,164,166,167,168,170,171,172
	db	173,174,175,177,178,179,180,181,182,183,184,185,186,188,189,190
	db	191,192,193,193,194,195,196,197,198,199,200,201,202,202,203,204
	db	205,206,207,207,208,209,210,210,211,212,213,213,214,215,216,216
	db	217,218,218,219,219,220,221,221,222,223,223,224,224,225,225,226
	db	226,227,227,228,229,229,230,230,230,231,231,232,232,233,233,234
	db	234,234,235,235,236,236,236,237,237,238,238,238,239,239,239,240
	db	240,240,241,241,241,242,242,242,242,243,243,243,244,244,244,244
	db	245,245,245,245,245,246,246,246,246,247,247,247,247,247,248,248
	db	248,248,248,248,249,249,249,249,249,249,249,250,250,250,250,250
	db	250,250,251,251,251,251,251,251,251,251,251,252,252,252,252,252
	db	252,252,252,252,252,252,252,253,253,253,253,253,253,253,253,253
	db	253,253,253,253,253,253,253,254,254,254,254,254,254,254,254,254
	db	254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,255
	db	255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255

VxD_Locked_Data_Ends

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

VxD_Locked_Code_Seg
;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: Funktionsergebnis, Zentrierung wie gewünscht
		;VR: AX
	inc	ah
GetSin:
	test	ah,bit 0
	jz	@@1	;1. oder 3. Quadrant
	not	al	;Bit-Komplement bilden
@@1:	BTST	[PMotor.CFlags],bit MC_Table	;Sinus/Dreieck-Schalter
	BT	[dword PMotor.CFlags],MC_Trapeze
	jnz	@@dreieck
	push	ebx
	 lea	ebx,[SinTab]
	 jnc	@@1a
	 lea	ebx,[KurvTab]
@@1a:	 xlat			;Tabellenzugriff
	pop	ebx
	jmp	FlipAL
@@dreieck:
	jc	@@trapez
	stc		;0-->80, 1-->80, 2-->81, 3-->81, 4-->82,...
	rcr	al,1	;FD-->FE, FE-->FF, FF-->FF (Erster Quadrant)
	jmp	FlipAL
@@trapez:
	add	al,80h	;00-->80, 01-->81, ... 7F-->FF,
	jnc	FlipAL
	sbb	al,al	;AL:=FF,   80-->FF, 81-->FF, .. FF-->FF
FlipAL:	test	ah,bit 1	;3./4. Quadrant
	jz	FlipSign	;nein, positiv lassen
	not	al	;ja, 80-->7F, 81-->7E,... FF-->00
FlipSign:
;Das verflixte Viech arbeitet mit Vorzeichen/Betrag!
;Deshalb "Fast-Food-Patch"
	BTST	[PMotor.CFlags],bit MC_Signed
	jz	@@e
	BTST	al,bit 7
	jnz	@@e
	xor	al,7Fh
@@e:	ret
endp GetCos

;*****************************
;*** Portzugriffe auf CMOS ***
;*****************************

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

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

;Ausschalten des Stroms durch die Spulen
;PE: EBX: Objektzeiger
;VR: AX
proc Motor_Aus
	mov	al,128
	call	FlipSign
	mov	edx,[PMotor.Ports]
	out	dx,al
	shr	edx,16
	out	dx,al
	BRES	[PMotor.Flags],bit MF_Synced
	ret
endp Motor_Aus

;Allokieren des Motors; Spulenstrom sicherheitshalber ausschalten
;PE: EBX: Objektzeiger
;VR: AX
proc Motor_Init
	pushad
	 call	Motor_Aus
	 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
	popad
	ret
endp Motor_Init

;Motor um vzb. Betrag bewegen, dieser sollte -256..256 nicht überschreiten.
;Dabei wird die Positions-Arbeitszelle mitgeführt
;PE: EAX: vzb. Betrag (möglichst -256..256)
;    EBX: Objektzeiger
;VR: AX
proc motor_move
	BTST	[PMotor.CFlags],bit MC_AlwaysSynced
	jnz	@@0		;Position IMMER mitführen
	BTST	[PMotor.Flags],bit MF_Synced
	jz	@@1		;Position NICHT mitführen, wenn unbekannt
@@0:	add	[PMotor.Pos],eax
@@1:	add	[PMotor.HWPos],ax
	mov	ax,[PMotor.HWPos]
	mov	edx,[PMotor.Ports]
	push	eax
	 call	GetCos
	 out	dx,al
	pop	eax
	call	GetSin
	shr	edx,16
	out	dx,al
	ret
endp motor_move

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

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

proc intreffahrt
IFDEF LogInt
Debug_Out "IntSync "
ENDIF
	call	TestSchalter		;Hardware-Endschalter abfragen
	jnz	@@1			;Treffer - Schalter betätigt
	xor	eax,eax
	BTR	[PMotor.Flags],MF_NegDir	;(+) Richtung ZUM Taster
	jc	@@3			;im Wendepunkt 1x aussetzen
@@5:	mov	eax,[PMotor.RefSpeed]
@@3:	call	MoveBySpeed
	ret
@@1:
	BTST	[PMotor.Flags],bit MF_NegDir	;(-) Richtung VOM Taster?
	jnz	@@5			;War's schon: weiter rückwärts!
;neu: kein "Einrütteln" in exakte Position, sondern
;Aufaddieren der Hardware-Position zum Referenzpunkt
	mov	eax,[dword PMotor.HWPos]	;10-Bit-Zahl holen
	shl	eax,22
	sar	eax,22	;Einfachste Möglichkeit der Vorzeichenerweiterung
	add	eax,[PMotor.Refpoint]
	mov	[PMotor.Pos],eax	;Das ist nun die Position
@@br	equ	%((bit MF_InMove) or (bit MF_InSync) or (bit MF_HWECheck))
	BRES	[PMotor.Flags],@@br
	BSET	[PMotor.Flags],bit MF_Synced
	call	InformWindows
	ret
endp intreffahrt

proc intmoveproc
IFDEF LogInt
Debug_Out "IntMove "
ENDIF
	mov	eax,[PMotor.WegLo]
	or	eax,[PMotor.WegMid]	;Nichts mehr tun?
	jz	@@1
	BTST	[PMotor.CFlags],bit MC_Observe ;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],ax
	mov	[PMotor.Pos],eax
	BRES	[PMotor.Flags],bit MF_Synced
	BSET	[PMotor.Flags],bit MF_HWECheck
@@1:
	mov	[PMotor.Speed],eax
	BRES	[PMotor.Flags],bit MF_InMove ;Bewegungssegment ENDE
	call	InformWindows
	ret
endp intmoveproc

proc intproc		;Das Innere des Timer-Interrupts
	mov	esi,[ListHand]
	cli
	VMMCall	List_Get_First
	sti
	jz	@@e
@@l:	push	esi
	 xchg	ebx,eax		;EBX=Objektzeiger
	 BTST	[PMotor.Flags],bit MF_InMove
	 jz	@@o
	 BTST	[PMotor.Flags],bit MF_InSync
	 jz	@@m
	 call	intreffahrt
	 jmp	@@o
@@m:	 call	intmoveproc
@@o:	 xchg	eax,ebx
	pop	esi
	cli
	VMMCall	List_Get_Next
	sti
	jnz	@@l
@@e:	inc	[upcounter]
	ret
endp intproc

;*******************************
;*** Interruptserviceroutine ***
;*******************************
;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
	VxDcall	VPICD_Phys_EOI
	clc			;ISR behandelte IRQ
	ret
@@chain:
;### BEGIN PATCH 15:26 14.04.1999 ###
	int	3
;### END PATCH 15:26 14.04.1999 ###
	Debug_Halt
	popad
	stc			;weiter verketten!
	ret
endp CmosIsr

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

proc StartSegment
;PE: EAX=vzb. Differenz
@@rb	equ	%((bit MF_NegDir) or (bit MF_InSync))
;### BEGIN PATCH 15:25 14.04.1999 ###
	pushad
	 mov	ah,0bh
	 call	GetCMOS
	 BRES	al,bit 6
	 call	PutCMOS
	 BSET	al,bit 6	;CMOS Interrupt 1024Hz aktivieren
	 call	PutCMOS
	 mov	eax,[IrqHand]
	 VxDcall VPICD_Phys_EOI	;evtl. verklemmten PIC befreien
	popad
;### END PATCH 15:25 14.04.1999 ###
	BRES	[PMotor.Flags],@@rb	;Positiv annehmen, keine RefFahrt
	sub	eax,[PMotor.Pos]	;Differenz
	jge	@@1
	neg	eax			;Betrag bilden
	BSET	[PMotor.Flags],bit MF_NegDir ;und Richtung merken
@@1:	push	eax
	 xor	eax,eax
	 mov	[PMotor.Speed],eax	;Geschwindigkeit nullsetzen
	 mov	[PMotor.WegLo],eax
	pop	eax
	mov	[PMotor.WegMid],eax	;und Weg entsprechend setzen
	BSET	[PMotor.Flags],bit MF_InMove ;Und LOS!
	ret
endp

proc StartReffahrt
	mov	[PMotor.Speed],0
@@sb	equ	%((bit MF_InMove) or (bit MF_InSync) or (bit MF_NegDir))
	BSET	[PMotor.Flags],@@sb	;ISR werkeln lassen
	ret
endp

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

proc PostEventProc
	mov	ebx,edx		;geordnete Verhältnisse
	cmp	[PMotor.CallbackAddr],0
	jz	@@e
	Debug_Halt
	Push_Client_State USES_EDI
	VMMCall	Begin_Nest_Exec
	mov	eax,[PMotor.CallbackUser]
	mov	[PClient._EDX],eax
	shr	eax,16
	mov	[PClient._ECX],eax
	mov	eax,[PMotor.Pos]
	mov	[PClient._ESI],eax
	shr	eax,16
	mov	[PClient._EDI],eax
	mov	al,[PMotor.Handle]
	mov	ah,[byte PMotor.Flags]
	mov	[PClient._EAX],eax
	mov	ax,[PMotor.CallbackDS]
	mov	[PClient._DS],ax
	movzx	edx,[word LOW PMotor.CallbackAddr]
	mov	cx,[word HIGH PMotor.CallbackAddr]
	VMMCall	Simulate_Far_Call
	VMMCall	Resume_Exec
	VMMCall	End_Nest_Exec
	Pop_Client_State USES_ESI
@@e:	ret
endp

proc InformWindows c
uses ebx,ecx,edx
	mov	edx,ebx
	mov	eax,High_Pri_Device_Boost
	mov	ebx,[PMotor.CallbackVM]
	LD	ecx,PEF_Wait_For_STI or PEF_Wait_Not_Crit or PEF_Always_Sched
	lea	esi,[PostEventProc]
	VMMCall	Call_Priority_VM_Event
	ret
endp

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

;PE: EAX=vzl. (oder auch vzb.) Geschwindigkeit in Nanoschritten
;    EBX=Objektzeiger
;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],MF_NegDir	;Richtungs-Bit nach CY
	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, E(EDX) immer 0)
;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	dx,[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	dx,[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

	%SUBTTL "V86- und PM-API"
;*********************************************
;*** Testfunktionen mit Fehlercoderückgabe ***
;*********************************************

;Testet, ob HWE betätigt wurde, und ggf. Fehler melden
proc TestHWE
	BTST	[PMotor.Flags],bit MF_HWECheck	;Endschalter betätigt?
	jz	@@e		;nein, okay
	mov	al,ME_HardEnd
	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 MF_InMove	;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, und lädt anschließend EAX aus Client_CX:DX
proc TestReferenz
	call	TestStill
	jc	@@e
	BTST	[PMotor.CFlags],bit MC_SyncRequired
	jz	@@w
	BTST	[PMotor.Flags],bit MF_Synced	;Ungültig?
	jnz	@@w		;Gültig!
	mov	al,ME_NoRef
	stc
@@e:	ret
@@w:	mov	ax,[PClient._CX]
	shl	eax,16
	mov	ax,[PClient._DX]
	clc
	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

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

proc SMGetVer
	mov	[PClient._DX],UMSTEP_Version
	clc
	ret
endp

;*************************************
proc SMSetMotor
;PE: Client_ES:BX=Konstante Motor-Struktur mit
;    Handle=0 für neuen Motor (ggf. Zwangszuweisung bei gleichen Portadressen)
;    Handle<>0: Daten für vorhandenen Motor neu setzen
;    Komponente "Handle" wird ggf. vom VxD verändert
;    Client_AL=0: Bei auffindbarer gleicher Portadresse Fehler!
	Client_Ptr_Flat ebx,ES,BX,USES_EAX
	add	eax,1
	mov	al,ME_InvalPointer
	jc	@@e		;Ungültige Adresse in ES:BX
	mov	al,ME_InvalPort
	cmp	[PMotor.PortA],1
	jc	@@e		;Ungültige PortA-Adresse
	cmp	[PMotor.PortB],1
	jc	@@e		;Ungültige PortB-Adresse
;Erst einmal passenden Listeneintrag suchen
	cli
	mov	esi,[ListHand]
	VMMCall	List_Get_First
	jz	@@o1		;Noch leere Liste
@@l1:	push	eax
	 mov	edi,eax		;Listen-Knoten
	 mov	al,[PMotor.Handle]
	 or	al,al		;Neuer Motor?
	 jz	@@n1
	 cmp	[(TMotor edi).Handle],al	;Gleiches Handle?
	 jne	@@nf
	 jmp	@@e1
@@n1:	;Portadressen vergleichen - bei Gleichheit Fehler oder Zwangszuweisung
	 mov	eax,[PMotor.Ports]
	 cmp	[(TMotor edi).Ports],eax
	 jne	@@nf
	 cmp	[PClient._AL],1	;Fehlermeldung bei gleichen Ports erzwingen?
	 mov	al,ME_AlreadyAssigned
	 jc	@@e2		;ja, raus!
	 mov	al,[(TMotor edi).Handle]
	 mov	[PMotor.Handle],al	;Handle rückmelden bei gleichen Ports
@@e1:	pop	eax
	call	EditNode
	clc
	jmp	@@e
@@e2:
	pop	eax
	jmp	@@e		;raus mit Fehler
@@nf:
	pop	eax
	VMMCall	List_Get_Next
	jnz	@@l1
@@o1:
	cmp	[PMotor.Handle],0	;Null?
	stc
	mov	al,ME_InvalHandle
	jnz	@@e		;Nicht null: Fehler!
	bsf	edx,[HandlesUsed]
	mov	al,ME_OutOfHandles
	jz	@@e		;Fehler - nichts frei!
	btr	[HandlesUsed],edx	;Bit als belegt kennzeichnen
	mov	[PMotor.Handle],dl	;Neues Handle rückmelden
@@2:
	VMMCall	List_Allocate	;Listen-Knoten beschaffen
	push	eax
	 xchg	edi,eax
	 mov	al,ME_OutOfMemory
	 jc	@@e2
	 LD	ecx,TMotor_ConstLongs+TMotor_VarLongs
	 xor	eax,eax
	 rep	stosd		;Gründlich löschen
	pop	eax
	call	EditNode
	mov	ebx,eax
	call	Motor_Init
	VMMCall	List_Attach	;und dann einfügen
	clc
@@e:	sti
	ret
endp

proc GetPMotor
;PE: AL=Handle
;PA: EBX=Zeiger auf Motor-Struktur
;    EDX=Handle (nullerweitert)
;    ESI=Listen-Handle
;    EAX=EBX=Motor-Struktur
;    CY=1: Nicht gefunden, dann AL=Fehlercode ME_
;VR: ESI,EAX,EBX,EDX
	movzx	edx,al
	add	al,-32
	mov	al,ME_InvalHandle
	jc	@@e		;Fehler: Handle>31
	bt	[HandlesUsed],edx
	jc	@@e		;Fehler: Unbenutztes Handle!
	cli
	mov	esi,[ListHand]
	VMMCall	List_Get_First
	jz	@@o1		;Noch leere Liste
@@l1:	mov	ebx,eax
	cmp	[PMotor.Handle],dl
	jz	@@e		;gefunden! CY=0
	VMMCall	List_Get_Next
	jnz	@@l1
@@o1:	mov	al,ME_InternErr
	stc
	sti
@@e:	ret
endp
;*************************************
proc SMGetMotor
;PE: Client_AL=AL=Handle
;    Client_ES:BX=Stückel Speicher
	call	GetPMotor
	jc	@@e		;Fehler: Nicht zu finden!
	Client_Ptr_Flat eax,ES,BX
	mov	edi,eax
	add	eax,1
	mov	al,ME_InvalPointer
	jc	@@e		;Ungültige Adresse in ES:BX
	mov	esi,ebx
	LD	ecx,TMotor_ConstLongs
	rep	movsd		;Daten liefern
	clc
@@e:	ret
endp

;*************************************
proc SMRemoveMotor
;PE: Client_AL=AL=Handle
	call	GetPMotor
	jc	@@e
	call	Motor_Aus	;Motor freischalten
	bts	[HandlesUsed],edx
	mov	eax,ebx
	VMMCall	List_Remove
	mov	al,ME_InternErr
	jc	@@e
	xchg	eax,ebx
	VMMCall	List_Deallocate
	clc
@@e:	ret
endp

;*************************************
proc SMSetIntFreq	;Dummy: Nichts tun!
	mov	ax,[PClient._BX]
	clc
@@e:	ret
endp

;*************************************
proc SMGetIntFreq
	mov	ax,[Frequency]
	mov	[PClient._BX],ax
	clc
@@e:	ret
endp

;*************************************
proc SMSync	;Referenzfahrt
	call	GetPMotor
	jc	@@e
	call	TestStill
	jc	@@e
	call    StartReffahrt
@@e:	ret
endp

;*************************************
proc SMMoveAbs	;Absolutbewegung
	call	GetPMotor
	jc	@@e
	call	TestReferenz
	jc	@@e
	call	TestInBounds
	jc	@@e
	call	StartSegment
@@e:	ret
endp

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

;*************************************
proc SMStop
	call	GetPMotor
	jc	@@e
	btr	[PMotor.Flags],MF_InMove;ISR-Arbeit radikal stoppen
	jnc	@@e
	call	InformWindows		;Callback rufen, wenn in Bewegung!
	clc
@@e:
	ret
endp

;*************************************
proc SMFree	;Motorspulen stromfrei schalten
	call	SMStop
	jc	@@e
	BTST	[PMotor.CFlags],bit MC_SyncedOnFree
	jnz	@@1
	BRES	[PMotor.Flags],bit MF_InSync
@@1:	call	Motor_Aus
	clc
@@e:	ret
endp

;*************************************
proc SMGetPosition	;liefert Position und Geschwindigkeit
	call	GetPMotor
	jc	@@e
	cli		;Ein zusammenhängendes Paar erwischen!
	mov	eax,[PMotor.Pos]
	mov	edx,[PMotor.Speed]
	mov	cl,[byte PMotor.Flags]
	sti
	mov	[PClient._EDX],eax
	shr	eax,16
	mov	[PClient._ECX],eax
	xchg	eax,edx
	mov	[PClient._ESI],eax
	shr	eax,16
	mov	[PClient._EDI],eax
	mov	[PClient._AH],cl
	jmp	SMGetIntFreq
@@e:
	ret
endp

;*************************************

proc EditNode
;PE: EAX=Knoten (Ziel), EBX=User-Struktur(Quelle)
;    Die aktuelle VM wird zur Callback-VM erklärt
	pushad
	 xchg	edi,eax
	 xchg	esi,ebx
	 LD	ecx,TMotor_ConstLongs
	 rep	movsd
	 VMMCall Get_Cur_VM_Handle	;VM-Handle setzen
	 mov	[((TMotor edi)-4*TMotor_ConstLongs).CallbackVM],ebx
	popad
	ret
endp EditNode

VxD_Locked_Code_Ends

;*************************************
;*** Neue API - Funktionsverteiler ***
;*************************************
VxD_Locked_Data_Seg

HandlesUsed	dd	0FFFFFFFEh
;Bitmaske für verwendete Handles
;0=belegt, 1=frei; Bit0 ist immer "belegt"
;Suche freier Bits erfolgt mit BSF

;Unterprogramm-Verteilertabelle
upvt	dd	OFFSET SMGetVer		;Versionsnummer holen
	dd	OFFSET SMSetMotor	;Motor hinzufügen, Parameter setzen
	dd	OFFSET SMGetMotor	;Motorparameter holen
	dd	OFFSET SMRemoveMotor	;Motor entfernen
	dd	OFFSET SMSetIntFreq	;Interruptfrequenz setzen
	dd	OFFSET SMGetIntFreq	;Interruptfrequenz holen
	dd	OFFSET SMSync		;Referenzfahrt
	dd	OFFSET SMMoveAbs	;Absolutbewegung
	dd	OFFSET SMMoveRel	;Relativbewegung
	dd	OFFSET SMStop		;Soforthalt
	dd	OFFSET SMFree		;Motorspulen stromfrei schalten
	dd	OFFSET SMGetPosition	;Position und Geschw. erfragen
VxD_Locked_Data_Ends

VxD_Locked_Code_Seg

BeginProc UMSTEP_Api
	pushad
	mov	ax,[PClient._AX]	;AX laden
	cmp	ah,SM_LastFunc
	ja	@@NoFunc
Debug_Out "ApiCall Fkt=#AH"
	movzx	ecx,ah
	call	[upvt+ecx*4]
	jc	@@err			;Bei Carry AL zurückgeben
	xor	al,al			;sonst 0 = Kein Fehler
	BRES	[PClient._Flags],CF_Mask	;Carry löschen
	jmp	@@NoErr
@@NoFunc:
	mov	al,ME_WrongFunction
@@Err:
	BSET	[PClient._Flags],CF_Mask	;Carry setzen
@@NoErr:
	mov	[PClient._AL],al
	popad
	ret
EndProc UMSTEP_Api

;*********************
;*** Exit-Funktion ***
;*********************
proc UMSTEP_VMDies
;Wenn eine VM stirbt, müssen zugehörige Motoren "entfernt" werden!
	mov	esi,[ListHand]
	mov	edi,ebx		;Aktuelle VM
	pushf
	 cli
@@vorn:	 VMMCall List_Get_First
	 jz	@@o
@@l:	 xchg	ebx,eax
	 cmp	[PMotor.CallbackVM],edi
	 jnz	@@nm
	 call	Motor_Aus	;Freischalten!
	 mov	eax,ebx
	 VMMCall List_Remove
	 xchg	eax,ebx
	 VMMCall List_Deallocate
	 jmp	@@vorn		;Liste noch einmal von vorn durchgehen!
@@nm:	 xchg	eax,ebx
	 VMMCall List_Get_Next
	 jnz	@@l
@@o:	popf
	ret
endp UMSTEP_VMDies


proc UMSTEP_Exit
UMSTEP_Dynamic_Exit:
Debug_Out "UMSTEP_Exit"
	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
	mov	esi,[ListHand]
	VMMCall List_Destroy
	ret
endp

VxD_Locked_Code_Ends

;******************************
;*** Installations-Funktion ***
;******************************
VxD_IData_Seg
IrqDesc	VPICD_Irq_Descriptor <8,0,OFFSET32 CmosIsr>
VxD_IData_Ends

VxD_ICode_Seg

BeginProc UMSTEP_Init
;* IRQ8 freischalten
;* Portadresse aus SYSTEM.INI holen
;* Ports trappen
;* Motoren zurücksetzen
Debug_Out "UMSTEP_Init"
UMSTEP_Dynamic_Init:
	LD	eax,LF_Async
	LD	ecx,size TMotor
	VMMCall	List_Create
	jc	@@e
	mov	[ListHand],esi
	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	;fest in diesem Programm
	clc
@@e:
	ret
EndProc UMSTEP_Init

VxD_ICode_Ends

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