03. december 2002 - 14:13Der er
16 kommentarer og 1 løsning
Windows, interrupts, events og handels
Jeg er pt. ved at skrive et program der styre noget hardware tilknyttet et I/O-kort. Samme I/O-kort har nogle ubenyttede 8254 timer/counter kredse, som jeg skal bruge til at opnå en meget eksakt timing, vha. interrupts, med (1000-2500 gange i sekundet). Med kortet medfølger en .dll, for at lette brugen af kortet under windows. Opsætningen af timerne er ikke svær, men jeg er rimeligt meget itvivl om hvordan jeg sætter interrupt funktionaliteten op, således at en funktion i mit program bliver kaldt ved hver interrupt. Følgende er afskrift fra dokumentationen, der vedrører interrupt-funktionerne i .dll filen:
--- W_INT_Enable() The function is used to initialize and startup the interrupt control. After calling this function, every time an interrupt request signal is generated, a software event is signaled. So that in your program, you can use operation wait for the event. When the event is signaled, it means an interrupt is generated NOTE: W_INT_Disable() must be called before the program terminates.
MSC/C++ Syntax: int W_INT_Enable(int interruptno, HANDLE *phEvent);
Arguments: interruptno : Normal PC interrupt number. Not shared.
Returns: 1 : NoError 0 : INT_Not_Set ---
Udfra ovenstående forstår jeg at W_INT_Enable funktionen kan lave et windows event - men jeg kender desvære ikke nok til windows/delphi til at vide hvordan jeg laver koden omkring det, så jeg kan få kaldt min funktion hver gang der kommer et event fra ovenstående funktion. Nogen der kan skrive en simpel kodestump der viser mig hvad jeg skal gøre.
Som et sekundært spørgsmål, så er jeg itvivl om hvorvidt jeg rent faktisk vil kunne opnå en timing der er god nok når der bruges windows-events. Derfor er jeg også intereseret i en alternativ løsning, hvor jeg kan få min funktion ind istedet for funktionen der laver eventet. Min funktion er stort set ikke andet end at skrive en byte til en I/O-portadresse, og er med sikkerhed hurtigere end at benytte windows-events.
Programmet bliver udviklet til Win98SE, og behøver ikke at være portabelt til andre windows-platforme.
Jeg ser frem til at se hvilke løsninger der kan fremkomme på disse spørgsmål.
Jeg vil umiddelbart tro, at Windows events er for langsomme, hvis du har brug for en frekvens på 1000-2500 interrupts/sekund. Du skal nok overveje din egen driver, hvis ikke der følger nogle lidt mere velegnede funktioner med kortet, som du ikke har nævnt.
Hej Jesper, Meget interesant link du der giver... Men jeg kan se at det kræver en del mere arbejde end jeg umidelbart har tid til.
Men ifølge dokumentationen til det I/O-kort jeg benytter, så skulle det være muligt at køre med op til 10000 interrupts/sec - på en 266MHz PC. (Under forudsætning af at man har en realtime/time-critical task, og ikke har noget imod at andre tasks bliver forsinket...) Så jeg regner fortsat med at ville prøve at benytte de funktioner der følger med kortet. Det betyder at jeg er tilbage ved mit primære spørgsmål: Hvordan man laver en handler/event ting under delphi.
Jeg er stadig itvivl om hvordan hele dette handler/event-cirkus fungerer.
Tildeler jeg en handler til den funktion jeg ønsker aktiveret, og bruger den som en pointer i W_INT_Enable(), således at min funktion automatisk bliver aktiveret ved interrupt.
eller
Sætter jeg en handler op, som giver mulighed for at affyre et windows-event (som vel reelt bare er det unikke handler-nummer der bliver afsendt i en eller anden event-stream), og derved trigger min funktion som står og venter på netop det unikke event?
Efterhånden som jeg fordøjer det jeg har læst, tror jeg mest på det sidste. Det første ligner mere hvad man laver i embeddede-systemer, eller f.eks. i DOS - med direkte kald af en funktion i et interrupt. Men som følge af min nye forståelse, betyder det at jeg har brug for at vide hvordan jeg opretter det handle, og hvordan jeg laver en funktion der trigger på det specifikke event.
Det er sikkert noget istil med:
creat handle : MyHandle W_INT_Enable(9,MyHandle)
function onMyHandle(trigger){ // foobar }
Jeg synnes bare ikke jeg kan finde noget information i online-help'en, eller på nettet der beskriver det ordenligt.
Efter lang tids søgen fandt jeg noget der måske kan hjælpe lidt på forståelsen. Det er en lille del af et C program, der er skrevet til det I/O-kort jeg benytter:
HANDLE hIntEvent;
//use extra 8254 timer pacer to generate interrupt W_INT_Enable(IRQ9, &hIntEvent);
if ((err=WaitForSingleObject(hIntEvent, 1000))== WAIT_OBJECT_0) { // foobar }
I teorien er det muligt at implementerer ovenstående direkte i Delphi... MEN jeg bryder mig ikke om at skulle bekymre mig om at benytte WaitForSingleObject, hvilket vil kræve at jeg laver en busy-wait funktion... Derimod vil jeg hellere benytte det event direkte til at starte en funktion. På samme måde som man f.eks. benytter onClick og onExit. Men hvordan gør jeg det?
Det lyder rimeligt omstændigt at skulle lave en thread, og kalde vidre derigennem... Er der ikke et alternativ til WaitForSingleObject, der gør tingene mere direkte, og derfor har mindre overhead?
Jeg prøver at sætte en thread op, og ser om jeg kan få det til at spille. Men jeg vil fortsat gerne se den kode du har liggende - hvilken sikkert indeholder ting jeg endnu ikke har fundet ud af i forbindelse med Delphi og specielt med Windows. (Hvis det ikke er gået op for jer endnu, så er jeg altså ikke vant til at programerer under Win - ej. heller i Delphi.)
Jeg har nu arbejdet på dette problem hele dagen, og er ikke rigtig kommet til en brugbar løsning. Nedenfor er mit forsøg på at starte en ny thread, og så der checker om der er kommet en interrupt-event.
Jeg har inkluderet nok til at det skulle være muligt at se hvad jeg gør, og hvorfor. Vær opmæsksom på at dll funktionernes navne nu er ændret til deres reele navne, og ikke dem jeg tidligere kaldte dem - der er også kommet lidt flere argumenter med, noget der alt sammen er triviel info I ikke skal bekymre jer om.
Når jeg køre nedenstående, får jeg en access-vaiolation fra adr. $FFFFFFFF ved kald af W_8454_INT_Enable(1,9,hInterruptEvent). Såvidt jeg kunne læse i debuggeren (x86 maskinkode er ikke just mit speciale), så skyldes det at hInterrupEvent indeholder den adresse - hvilket den helt sikkert ikke skal gøre... jeg kan bare ikke finde ud af hvorfor den gør det.
var Form1: TForm1; InterruptThread: TInterruptThread;
implementation
{$R *.DFM}
//*************************** // Special Interrupt thread. //***************************
constructor TInterruptThread.Create; begin if (W_8454_INT_Enable(1,9,hInterruptEvent) = INT_Not_Set ) then begin Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': No Interrupts.'); Form1.Global_CanRun := False; end else Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': Interrupts at 1Hz.'); SetPriorityClass(GetCurrentProcess, REALTIME_PRIORITY_CLASS);{} SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);{} hInterruptEvent := CreateEvent(nil,true,false,nil); inherited Create(false); end;
destructor TInterruptThread.Destroy; begin SetEvent(hInterruptEvent); // Release the wait. CloseHandle(hInterruptEvent); // Release Event if (W_8454_INT_Disable(1) = NoError) // Remove interrupt. then Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': InterruptThread terminated.') else Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': Error terminating InterruptThread.'); inherited; end;
procedure TInterruptThread.execute; begin case WaitForSingleObject(hInterruptEvent, INFINITE) of WAIT_FAILED : Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': Interrupt handler not pressent.'); WAIT_ABANDONED : Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': InterruptThread has been abandoned.'); WAIT_OBJECT_0 : Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': InterruptTrigger'); WAIT_TIMEOUT : Form1.StatusBox.Items.Insert(0,TimeToStr(Time)+': InterruptThread has timed out.'); end; end;
//********************** // Form init and destroy. //**********************
procedure TForm1.FormCreate(Sender: TObject); var t: longint; begin Global_CanRun := True; // Defaults to no problem. // Timer/Counter PIC initialization, and other vital things removed. InterruptThread.Create; InterruptThread.Execute; end;
procedure TForm1.FormDestroy(Sender: TObject); begin InterruptThread.Destroy; // Other vital termination functions removed. end;
Her er lidt kodestumper fra et par eksisterende og fungerende programmer, det er godt nok C, men mon ikke du kan få noget ud af det alligevel?
static HANDLE hEvent; /* event handle */
/* thread, which waits on the event */ DWORD WINAPI WaitEventThread( LPVOID param ) { time_t curtim;
tstout( "Start of WaitEventThread" ); if (hEvent != NULL) { tstout( "Start waiting for the first time" ); do { WaitForSingleObject( hEvent, INFINITE ); /* do something to handle the event */ } while (TRUE); } return TRUE; }
Det følgende er initialiseringskode, bemærk at eventet bliver skabt FØR tråden.
/* Since this program has #define UNICODE, the event name must be converted */ MultiByteToWideChar(CP_ACP, 0, "EVTNAME", 9, szBuf, 50);
Hej Jesper, Tak for dine svar. Du har helt ret i at jeg naturligvis skal kalde CreateEvent før jeg kalder W_8454_INT_Enable. CreateEvent er jo - trods alt - ikke synsk, og har ingen ide om hvilken event-handle den skal bruge. Nu er CreateEvent flyttet op, umidelbart før W_8454_INT_Enable. Desvære ser det ikke ud til at ændre noget. Jeg kikker nærmere på dit C eksempel lidt senere idag, og håber at kunne få det til at virke. Jeg er lidt itvivl om et par ting i dit C eksempel, primært i relation til Delphi. I delphi har jeg defineret en tråd kaldet TInterruptThread. Men du kalder OpenEvent(), og laver tilsyneladende en ny task ud af en procedure. Kan jeg gøre det samme i Delphi? Hvad er forskellen på, og hvilke fordele/ulæmper har, de forskellige metoder?
Jeg skal måske forklare lidt mere: De 2 første kodestumper er fra et og samme program, det er temmelig stort og hvad det gør er ikke relevant for diskussionen her. Vigtigt er, at der var behov for, at jeg kunne 'påkalde mig opmærksomhed' udefra. Dette er implementeret ved, at jeg i programmet opretter et event (med det eksternt tilgængelige navn "EVTNAME") og skaber en separat thread (navn WaitEventThread) indeni det store program, som udelukkende har til opgave at vente på eventet (sker med WaitForSingleObject). Når eventet indtræffer (hvilket opdages ved at WaitEventThread kommer tilbage fra WaitForSingleObject), udfører WaitEventThread det jeg nu ønskede at udføre (det jeg har kaldt /* do something to handle the event */). Derefter går den tilbage og venter på næste gang eventet indtræffer.
Den tredie kodestump er et helt separat og fritstående testprogram, som jeg lavede for at afprøve funktionen af de to ovenstående kodestumper. Det skaffer sig et handle til eventet (med OpenEvent under anvendelse af navnet "EVTNAME") og kalder PulseEvent - dette vil få WaitEventThread i det store program til at trille en omgang.
Jeg har ikke den store forstand på Delphi, så fordele og ulemper har jeg svært ved at udtale mig om. Hvad jeg dog kan se er, at du rundt omkring mangler at teste om dine Windows-kald går godt - et umiddelbart iøjnefaldende eksempel er dit kald af CreateEvent. Stilen er vistnok at hvis der returneres NULL skal man kalde GetLastError for at få den virkelige fejlkode (dette er ikke specifikt til Delphi, og du må slå op i Windows beskrivelserne for at få den virkelige sandhed). Det er en generel ting: Man skal ALTID teste på fejl, hvis man foretager sig noget, der kan gå galt! Som absolut minimum, skal man lave en fejludskrift, ellers kører programmet bare videre med et ubrugeligt resultat, og springer måske i luften meget senere, af en uigennemskuelig grund.
Din beskrivelse af dit program var ca. hvad jeg havde læst mig frem til selv. C koden er noget mere kompakt end det tilsvarende Delphi kode. Hvad angår check af fejl ved API kald, så har du helt ret i at der mangler check. Dette skyldes primært at det ikke var udført i den eksempelkode jeg havde fundet, og at jeg ikke havde tilføjet det på det tidspunkt hvor jeg sendte koden ud. (Jeg er vandt til at skrive embedded kode, hvor alting bliver testet og ført til dørs!) Noget helt andet er så, at jeg aldrig har fået en fejl fra den netop det kald -der er altid kommet et tal ud der så plausibelt ud.
Men jeg er blevet gjort klart fra en anden person, at min InterruptThread.Create; er udført forkert, samt at min InterruptThread.Execute; ikke er nødvendig. For at få create til at virke skal jeg skrive følgende:
InterruptThread := TInterruptThread.Create;
og så senere udføre InterruptThread.Terminate; for at lukke tråden igen. Lidt, men alligevel betydeligt anderledes end den måde jeg udførte tingene på.
Som det er nu kan jeg setevent() et event, og se det blive udført... Nu mangler jeg bare at få mit timer/counter kort til at virke.
Jesper, Tak for din hjælp. Det ser ud til at jeg kan få alt til at virke - dog ikke helt med den præcision jeg gerne ville.
Før jeg slutter helt vil jeg lige spørge om jeg stillede/dokumenterede mit spørgsmål helt dumt, eller om der er andre grunde til at så få kikkede forbi og gav en kommentar eller et svar med.
doc404, Tak for kommentaren. Jeg kender fint selv det med at tiden brænder på, så ingen sure miner herfra fordi du ikke fik postet noget kode.
Dette er faktisk mit første projekt med delphi og til windows, og jeg er meget overrasket over at det har været så svært at finde hjælp til så trivielle ting (i den "verden" jeg kommer fra :-) som at sætte interrupts, højopløslige timere osv. op. Ting som windows prioriteringsmekanisme er tilsyneladende også noget der ikke rigtigt at til at få styr på... Jeg har fundet 3-4 forskellige forklaringer på hvordan det virker, og de er delvist ens, men alligevel med store forskelle. Alt i alt, så oplever jeg at når jeg programerer til windows, så er den meget upræcis med ting jeg ønsker præcise (f.eks. bliver 1ms i timeren til mellem 0,5 og 15ms, med peaks på over 100ms). Til gengæld er den ekstrem præcis med ting som jeg forventede ville være meget mere varierende (f.eks. bliver 1ms i timeren til mellem 0,5 og 15ms, med peaks på over 100ms **UANSET** om man kører på en 266MHz pentium eller en 2GHz P4)
Nå, men det er vidst nok kommentare for mig lige nu...
umiddelbart så ud til at være en af de mere interessante.
Mvh Jesper Naur
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.