home *** CD-ROM | disk | FTP | other *** search
- (* ------------------------------------------------------ *)
- (* ATTIMER.PAS *)
- (* Die Unit »ATTimer« ermöglicht einen einfachen und *)
- (* sicheren Zugriff auf den RTC-Timer des AT. Der Timer *)
- (* kann auf eine (nahezu) beliebige Frequenz zwischen 2 *)
- (* und 8192 Hz eingestellt werden, Subroutinen des *)
- (* Anwenders ihrerseits mit einer eigenen Frequenz be- *)
- (* trieben werden. *)
- (* WICHTIG: Da mit der Unit ein Programm bis zu 8192 mal *)
- (* in der Sekunde aufgerufen wird, kann es gerade in *)
- (* Multitasking-Systemen zu einer Überlastung kommen, die *)
- (* bis zu einer totalen Blockierung des Systems führen *)
- (* kann. Die vom Timer aufgerufenen Routinen sollten daher*)
- (* möglichst kurz und schnell abzuarbeiten sein, sowie der*)
- (* Timer selbst mit einer möglichst geringen Frequenz *)
- (* installiert werden. *)
- (* (c) 1993 Jörn Eichler & DMV-Verlag *)
- (* ------------------------------------------------------ *)
- UNIT ATTimer;
-
- {$G+} (* UNIT nur ab 286 lauffähig *)
-
- INTERFACE
-
- USES Util;
-
- TYPE
-
- (* Kontrollblock für eine vom Anwender
- installierte Routine *)
-
- pUserProcHead = ^tUserProcHead;
- tUserProcHead = RECORD
- Code1 : ARRAY [1..3] OF BYTE;
- WaitCounter : WORD;
- WaitTicks : WORD;
- Code2 : ARRAY [1..14] OF BYTE;
- CallAddress : Pointer;
- Code3 : BYTE;
- JumpAddress : Pointer;
- Frequency : tFloat;
- END;
-
- CONST
- MaxFrequency = 8192;
- stRTCInstalled = 1;
-
- RTCFrequency : INTEGER = 0;
- RTCTimerCalls : LongInt = 0;
- RTCStatus : BYTE = 0;
-
- PROCEDURE InstallRTCTimer(Frequency : INTEGER);
- PROCEDURE DeInstallRTCTimer;
-
- FUNCTION InstallRTCUserProc(UserProc : Pointer;
- aFrequency : tFloat) : pUserProcHead;
- PROCEDURE DeInstallRTCUserProc
- (VAR Control : pUserProcHead);
-
- IMPLEMENTATION
-
- USES Dos;
-
- CONST
- IRQ8 = $70;
- RTCSelect = $70;
- RTCReadWrite = $71;
-
- VAR
- BIOSRunning : Byte ABSOLUTE $40:$A0;
- (* BIOS feststellen & sperren *)
- OldStatusA : WORD; (* RTC-Status sichern *)
- OldExitProc,
- OldIRQ8 : Pointer;
-
-
- PROCEDURE NoRTCProc; ASSEMBLER;
- (* Rücksprung der Userprocs *)
- ASM
- RETF
- END;
-
- CONST
- FirstRTCProc : pUserProcHead = @NoRTCProc;
- (* Liste der Userprocs *)
-
- PROCEDURE FastRTCTimer; ASSEMBLER;
- ASM
- STI
- PUSHA
- PUSH DS
- PUSH ES
-
- MOV AX, Seg @DATA (* DS mit Turbo-DS laden *)
- MOV DS, AX
-
- MOV AL, $0C (* RTC Statusbyte C lesen *)
- OUT RTCSelect, AL
- IN AL, RTCReadWrite
- TEST AL, $40 (* Ursache des Aufrufs *)
- JZ @@En
-
- ADD Word Ptr [RTCTimerCalls], 1
- ADC Word Ptr [RTCTimerCalls+2], 0
- CALL FirstRTCProc
- @@En:
- MOV AL, $20 (* EOI-Kommando an 8259 *)
- OUT $A0, AL (* AT-8259 ansteuern *)
- OUT $20, AL (* PC-8259 ansteuern *)
- POP ES (* Register vom Stack holen *)
- POP DS
- POPA
- IRET (* Interrupt beenden *)
- END;
-
- PROCEDURE SlowRTCTimer; ASSEMBLER;
- (* BIOS-kompatibler Timer *)
- ASM
- STI
- PUSHA
- PUSH DS
- PUSH ES
-
- MOV AX, Seg @DATA
- MOV DS, AX
-
- MOV AL, $0B (* RTC Steuerbyte B lesen *)
- MOV DX, RTCSelect
- OUT DX, AL
- INC DX
- JMP @@W1 (* Warteroutine *)
- @@W1:
- JMP @@W2
- @@W2:
- JMP @@W3
- @@W3:
- JMP @@W4
- @@W4:
- IN AL, DX
- MOV AH, AL
-
- MOV AL, $0C (* RTC Statusbyte C lesen *)
- MOV DX, RTCSelect
- OUT DX, AL
- INC DX
- JMP @@W5 (* Warten ... (s. Text) *)
- @@W5:
- JMP @@W6
- @@W6:
- JMP @@W7
- @@W7:
- JMP @@W8
- @@W8:
- IN AL, DX
- AND AL, AH (* Fehlaufrufe ausblenden *)
- TEST AL, $40 (* Ursache des Aufrufs der *)
- JZ @@En (* zyklische Interrupt? *)
-
- ADD Word Ptr [RTCTimerCalls], 1
- ADC Word Ptr [RTCTimerCalls+2], 0
- CALL FirstRTCProc
-
- @@En:
- MOV AL, $20 (* EOI-Kommando an 8259 *)
- OUT $A0, AL (* AT-8259 ansteuern *)
- OUT $20, AL (* PC-8259 ansteuern *)
- POP ES
- POP DS
- POPA
- IRET
- END;
-
- PROCEDURE WaitRTCPorts;
- (* 4 SHORT JMPS zum Warten *)
- INLINE($EB / $00 / $EB / $00 / $EB / $00 / $EB / $00 );
-
- PROCEDURE CLI; INLINE($FA);
- (* Interrupts sperren *)
- PROCEDURE STI; INLINE($FB);
- (* Interrupts freigeben *)
-
- PROCEDURE InstallRTCTimer(Frequency : INTEGER);
- (* Eingabe: *)
- (* Frequency = Frequenz, mit der Timer betrieben *)
- (* werden soll *)
- (* Ausgabe: *)
- (* RTCFrequency = tatsächliche Frequenz des Timers *)
- (* (0 = nicht installiert) *)
- (* RTCStatus = Status des Timers *)
- (* (Bit0 = 1: Timer installiert) *)
- VAR
- Exp : BYTE;
-
- FUNCTION GetExp : BYTE;
- (* Exponent zur Basis 2 holen *)
- VAR
- i : BYTE;
- Mask : WORD;
- BEGIN
- i := 12; (* max. 2^12 zulässig *)
- Mask := $1000; (* binäre Entsprechung (2^12) *)
- WHILE (i > 0) AND ((Mask AND Frequency) = 0) DO BEGIN
- (* 1. Bit suchen *)
- Dec(i);
- Mask := Mask SHR 1;
- END;
- IF (i = 0) THEN (* kein Bit gesetzt? *)
- GetExp := 1 (* Exponent = 1 (-> 2^1hz) *)
- ELSE IF (Frequency > Mask) THEN
- (* Frequenz nicht 2^x? *)
- GetExp := i + 1 (* nächsthöhere Frequenz *)
- ELSE
- GetExp := i;
- END;
-
- FUNCTION InstallTimer(TimerProc : Pointer) : BOOLEAN;
- VAR
- Dummy : BYTE;
- StartTicks,
- StartTimer : LongInt;
- BEGIN
- SetIntVec(IRQ8, TimerProc);
- (* neuen IRQ8 installieren *)
- Port[RTCSelect] := $A; (* Frequenz programmieren *)
- WaitRTCPorts;
- Port[RTCReadWrite] := $20 + ($F - (Exp - 1));
- Port[$A1] := Port[$A1] AND $FE;
- (* IRQ8 freigeben *)
- Port[RTCSelect] := $B;
- (* zyklischen Timer freigeben*)
- WaitRTCPorts;
- Dummy := Port[RTCReadWrite] OR $40;
- WaitRTCPorts;
- Port[RTCSelect] := $B;
- WaitRTCPorts;
- Port[RTCReadWrite] := Dummy;
-
- StartTicks := BIOSTimer;
- (* Installationserfolg testen *)
- StartTimer := RTCTimerCalls;
- WHILE (StartTimer = RTCTimerCalls) AND
- (( StartTicks + 9 ) > BIOSTimer) DO ;
- InstallTimer := (StartTimer <> RTCTimerCalls);
- END;
-
- BEGIN
- DeInstallRTCTimer;
- IF (Frequency >= MaxFrequency) THEN
- Exp := 13
- ELSE
- Exp := GetExp;
- GetIntVec(IRQ8, OldIRQ8); (* alten IRQ retten *)
- Port[$70] := $A; (* Status A der RTC sichern *)
- WaitRTCPorts;
- OldStatusA := Port[$71];
- IF NOT (InstallTimer(@FastRTCTimer)) THEN
- IF NOT (InstallTimer(@SlowRTCTimer)) THEN BEGIN
- Port[$70] := $A; (* Installation gescheitert *)
- WaitRTCPorts;
- Port[$71] := OldStatusA;
- SetIntVec(IRQ8, OldIRQ8);
- Exit;
- END;
- RTCFrequency := 1 SHL Exp;
- RTCStatus := stRTCInstalled;
- BIOSRunning := BIOSRunning OR 1;
- (* RTC für BIOS sperren *)
- END;
-
- PROCEDURE DeInstallRTCTimer;
- (* Deinstalliert den zuvor mit InstallRTCTimer *)
- (* installierten Timer und stellt den alten Status der*)
- (* RTC wieder her. Alle noch nicht deinstallierten *)
- (* Benutzerroutinen werden von DeInstallRTCTimer aus *)
- (* dem Speicher entfernt! *)
- VAR
- Dummy : BYTE;
- RunPtr : pUserProcHead;
- BEGIN
- IF ((RTCStatus AND stRTCInstalled) = stRTCInstalled)
- THEN BEGIN
- Port[RTCSelect] := $A;
- WaitRTCPorts;
- Port[RTCReadWrite] := OldStatusA;
- WaitRTCPorts;
- Port[RTCSelect] := $B;
- WaitRTCPorts;
- Dummy := Port[RTCReadWrite] AND $BF;
- WaitRTCPorts;
- Port[RTCSelect] := $B;
- WaitRTCPorts;
- Port[RTCReadWrite] := Dummy;
- SetIntVec(IRQ8, OldIRQ8);
- RunPtr := FirstRTCProc;
- WHILE (RunPtr <> @NoRTCProc) DO BEGIN
- FirstRTCProc := RunPtr^.JumpAddress;
- Dispose(RunPtr);
- RunPtr := FirstRTCProc;
- END;
- RTCFrequency := 0;
- RTCStatus := 0;
- RTCTimerCalls := 0;
- BIOSRunning := BIOSRunning AND $FE;
- END;
- END;
-
- {$F+}
- PROCEDURE UserProcHead; ASSEMBLER;
- (* Kontrollcode der Userprocs *)
- {$F-}
- ASM
- CALL @@GetIP (* IP auf den Stack bringen *)
- DW $9090 (* Frequenzmodulationszähler *)
- DW $9090 (* Frequenzmodulationswert *)
- @@GetIP:
- POP SI (* SI zeigt jetzt auf Daten *)
- DEC Word Ptr CS:[SI] (* Zähler erniedrigen *)
- JNZ @@END (* erst bei 0 Userroutine *)
- MOV AX, CS:[SI+2] (* zunächst Zähler neu *)
- MOV CS:[SI], AX (* initialisieren *)
- CALL UserProcHead (* call intersegment direct *)
- @@END:
- JMP UserProcHead (* jump intersegment direct *)
- END;
-
- FUNCTION InstallRTCUserProc(UserProc : Pointer;
- aFrequency : tFloat) : pUserProcHead;
- (* Installiert eine Anwenderroutine, die mit einer *)
- (* bestimmten Frequenz vom RTC-Timer aufgerufen wird. *)
- (* Die Routine muß dabei »far« definiert sein und *)
- (* zudem ohne Parameter aufgerufen werden. Sie sollte *)
- (* möglichst schnell abgearbeitet werden, damit das *)
- (* System nicht überlastet wird. Die Frequenz sollte *)
- (* möglichst eine Potenz von 2 sein, da ansonsten *)
- (* leicht Rundungsfehler auftreten. *)
- VAR
- Control : pUserProcHead;
- BEGIN
- InstallRTCUserProc := NIL;
- IF (UserProc = NIL) OR (aFrequency > RTCFrequency) OR
- ((RTCFrequency / aFrequency) > 65535) THEN Exit;
- New(Control);
- IF (Control = NIL) THEN Exit;
- Move(Pointer(@UserProcHead)^, Control^,
- SizeOf(tUserProcHead));
- WITH Control^ DO BEGIN
- WaitTicks := Round(RTCFrequency / aFrequency);
- WaitCounter := WaitTicks;
- CallAddress := UserProc;
- JumpAddress := FirstRTCProc;
- Frequency := aFrequency;
- END;
- CLI;
- FirstRTCProc := Control;
- STI;
- InstallRTCUserProc := Control;
- END;
-
- PROCEDURE DeInstallRTCUserProc(VAR Control : pUserProcHead);
- VAR
- RunPtr : pUserProcHead;
- BEGIN
- IF (Control = NIL) THEN Exit;
- IF (FirstRTCProc = Control) THEN BEGIN
- CLI;
- FirstRTCProc := pUserProcHead(Control)^.JumpAddress;
- STI;
- END ELSE BEGIN
- RunPtr := FirstRTCProc; (* Block suchen *)
- WHILE (RunPtr <> @NoRTCProc) AND
- (RunPtr^.JumpAddress <> Control) DO
- RunPtr := RunPtr^.JumpAddress;
- IF (RunPtr^.JumpAddress = Control) THEN BEGIN
- CLI;
- RunPtr^.JumpAddress :=
- pUserProcHead(Control)^.JumpAddress;
- STI;
- END ELSE
- Exit;
- END;
- Dispose(pUserProcHead(Control));
- Control := NIL;
- END;
-
- PROCEDURE ExitATTimer; FAR;
- BEGIN
- ExitProc := OldExitProc;
- DeInstallRTCTimer;
- END;
-
- BEGIN
- OldExitProc := ExitProc;
- ExitProc := @ExitATTimer;
- END.
- (* ------------------------------------------------------ *)
- (* Ende von ATTIMER.PAS *)
-
-