int21h

Použití časovače

Jestli jste někdy zkoušeli napsat nějakou hru, tak jste jistě narazili na problém, jak zajistit, aby běžela na různých počítačích stejně rychle. Začátečníci to někdy řeší tím, že nechají uživatele zadat jakýsi záhadný zpomalovací faktor, který se pak použije v proceduře Delay nebo v jiném čekači.
Trochu zkušenější provedou nějaký test rychlosti procesoru a výsledné číslo použijí stejně jako v předcházejícím případě. Tento způsob snad má své místo jako doplňující postup v nějakých speciálnostech, ale jako hlavní řešení se také nehodí.
V PC je proto pro tyto účely tzv. časovač. Časovač generuje v pravidelných intervalech pulzy, kterých se může programátor "chytit". Standardně se pulz generuje jednou za 55ms, což je přibližně 18,2x za vteřinu. Tato frekvence se dá ale změnit a my si za moment ukážeme jak.
Nejjednodušší způsob, jak sledovat časovač, je využití proměnné BIOSu na adrese 0040:006C.
V pascalu(TP i FP) tedy takto:
CONST rychlost = 5;
var d:longint;
begin
d:=MemL[Seg0040:$06c];
repeat
if MemL[Seg0040:$06c]>d+RYCHLOST then
   begin
   writeln('vypršel');
   timer_passed:=0;
   d:=MemL[Seg0040:$06c];
   end;
until keypressed;
while keypressed do readkey;
end.
Konstantou rychlost můžu určovat po kolika pulzech budu reagovat. Pokud bude 0, tak budu reagovat na každý pulz.

Tento postup má ale dost velkou nevýhodu. Sami si totiž musíme hlídat, kolik pulzů už proběhlo a kolik zbývá a zkrátka si to musíme dost hlídat. Elegantní je tedy odchycení přerušení časovače. Od teďka budu psát k programátorům ve FP (princip je ale naprosto stejný). Pro Turbo pascal je totiž návodů spousta, pro FP ale moc ne...
Hardwarové zpracování přerušení časovače funguje tak, že po každém tiku se přes kanál IRQ 0 zavolá přerušení BIOSu INT08h, které ho zpracuje. Tím se myslí, že se postará o proměnnou na 0040:006C, hlídá motory disketových mechanik a snad ještě něco. Potom ale ještě zavolá přerušení INT1C, které je určeno pro uživatelské doplňující rutiny. Doporučuje se tedy, obsazovat právě tento vektor, a ne přímo INT08h.
Ve Freepascalu to uděláme takto:
{$ASMMODE INTEL}
{$MODE FPC}
{$Q-}{$R-}{$S-}{$D-}
unit Timer;

interface
Procedure Zapni_Casovac;
Procedure Vypni_Casovac;
var pocet_tiku:byte;
    Nainstalovano:boolean;

implementation
uses  go32;
const int1c = $1c;
var timerproc:pointer;
    oldint1c : tseginfo;
    newint1c : tseginfo;
    BackupDS : Word; external name '___v2prt0_ds_alias';

procedure int1c_handler; assembler;interrupt;
asm
cli
push fs
push es
push ds
push ax
   mov ax,cs:[BackupDS]
   mov ds,ax
   mov es,ax
   mov ax,dosmemselector
   mov fs,ax
  call TimerProc
pop ax
pop ds
pop es
pop fs
sti
end;
Procedure HandlerDummy;begin end;

Procedure MujCasovac;
begin
inc(pocet_tiku);
OutPortB($20,$20);
end;
Procedure MujCasovacDummy;begin end;

Procedure Zapni_Casovac;
var i : Longint;
    counter:longint;
begin
timerproc:=@MujCasovac;
newint1c.offset := @int1c_handler;
newint1c.segment := get_cs;

lock_data(timerproc, sizeof(timerproc));
lock_data(dosmemselector, sizeof(dosmemselector));
lock_data(pocet_tiku, sizeof(pocet_tiku));
lock_code(@MujCasovac,longint(@MujCasovacDummy) - longint(@MujCasovac));
lock_code(@int1c_handler,longint(@HandlerDummy)-longint(@int1c_handler));
get_pm_interrupt(int1c, oldint1c);
set_pm_interrupt(int1c, newint1c);
pocet_tiku:=0;
Nainstalovano:=true;
end;

Procedure Vypni_Casovac;
begin
unlock_data(timerproc, sizeof(timerproc));
unlock_data(dosmemselector, sizeof(dosmemselector));
unlock_data(pocet_tiku, sizeof(pocet_tiku));
unlock_code(@MujCasovac,longint(@MujCasovacDummy) - longint(@MujCasovac));
unlock_code(@int1c_handler,longint(@HandlerDummy)-longint(@int1c_handler));
set_pm_interrupt(int1c, oldint1c);
Nainstalovano:=false;
end;

begin Nainstalovano:=false;
end.
Nic hrozného, ne? Jestli jste v minulém čísle četli mimořádně odborný, skvěle napsaný a hlavně MŮJ :-)) článek o chráněném režimu, tak vidíte, že je to druhý typ obsluhy přerušení - totální náhrada. Zajímavé je, že v tomhle případě (ale možná jenom na mém PC), se nic nestane, jestli vypustíte řádek OutPortB($20,$20);
Procedura nám samostatně zvyšuje čítač a hlavní program má o něco jednodušší úlohu. Ještě zdůrazněme, že ačkoliv tu totálně nahrazujeme přerušení INT1C, tak BIOS nadále přičítá do čítače na 0040:006C, protože INT1Ch je jen jakousi "odbočkou" INT08h.

Na začátku jsem se zmínil o tom, že je možné měnit frekvenci časovače. To je velice dobře, protože frekvence 18,2Hz je dost nízká. Například animace přehrávaná touto rychlostí není zcela plynulá. Časovač obsazuje porty 40h43h, které obsluhují 3 nezávislé kanály. Všechen dosavadní text je věnován kanálu 0, který je napojen právě na IRQ 0 - tedy na INT08h. Zbylé dva jsou občerstvovač paměti (aby se navymazala) a ovládání repráčku.
Změníme tedy nastavení kanálu nula: Do portu se to zadává v nějakých pochybných jednotkách, a proto musíme udělat přepočet:
Procedure NastavFrekvenci(f:longint); {F=pocet pulzu za sekundu}
var i:longint;
begin
i:=$1234DD div f;
OutPortB($43,$34);
OutPortB($40,counter mod 256);
OutPortB($40,counter div 256);
end;

Na konci programu všechno vraťte do původního stavu:
Procedure VratFrekvenci;
begin
OutPortB($43,$34);
OutPortB($40,0);
OutPortB($40,0);
end;
Tyto rutiny je vhodné vložit do procedur Zapni_Casovac a Vypni_Casovac z uvedeného příkladu. Abych řekl pravdu, tak ani nevím, do jakých hodnot je možné frekvenci časovače hnát. Pro tvorbu her ale extrémní hodnoty nejsou třeba :-)
Ve hrách se také často kvůli plynulosti animace doporučuje čekat při vykreslování na vertikální návrat paprsku. Můžete se dokonce rozhodnout se na časovač vybodnout a synchronizovat se jenom takhle. Pokud si ale frekvenci nenastavíte sami, což často windows ani nedovolí, tak ale nevíte, jakou máte. V módu 13h to snad bývá 70Hz, ale ten asi používat nebudete. V lepších režimech je to jak u koho a jak kdy...
Já osobně jsem ale nikdy žádné trhání při ignorování obnovovací frekvence nepozoroval a ve svých programech dávám synchronizaci s paprskem volitelně - pokud si to uživatel vypne, tak se může běh programu zrychlit. Já ji mám vypnutou vždy.
2006-11-30 | Laaca
Datum: 2.1.2009 22:16
Od: Lasik
Titulek: Bez titulku
Mam takový blbý dotaz ... může se něco stát s PC nebo systémem, když "vynuluju" ten časovač?
MemL[Seg0040:$06c]:=0;
Já předpokládám že ne, ale chci mít jistotu :)
Datum: 19.2.2009 13:33
Od: Martin Lux
Titulek: Tip
Teoreticky ne.. Je možné, že to shodí Windows 98. Ale přímý dopad na data na disku by to mít nemělo..
Reklamy: