30. juli 2004 - 10:38Der er
31 kommentarer og 1 løsning
Timer interval overholdes ikke.
Jeg har tidligere fået hjælp til at vise temperaturmålinger som en kurve i en chart, se spørgsmål "Tegning af kurve ved temperaturmåling". En OnTimer event læser værdien af en float, konverterer denne til en string, og adder den derefter til en StringList. Denne StringList vises så som en kurve i en chart. Hvis timer interval sættes til 50 mSek., sker opdateringen som den skal. Hvis man har valgt, der skal køres 1200 målinger, sker dette på 60 sek., dvs., opdateringstiden på 50 mSek. overholdes. Problemet er, hvis man sætter tiden ned til 10 mSek., kan programmet ikke følge med. Dvs., hvis man har valgt, der skal køres 2000 målinger, sker dette IKKE i løbet af 20 sek., men nærmere 48 sek. Problemet bliver værre, hvis man vælger at køre 2000 målinger i stedet for f.eks. 1000. Problemet bliver ligeledes værre, jo større mit program bliver.
Skal der benyttes en anden form for timer, eller hvad er løsningen på problemet?
Ps. løsningen med at bruge en StringList passer mig fint, da den er enkel (at forstå).
Det skyldes at nøjagtigheden af en TThimer ikke er bedere end 50ms.
Jeg har en Timer komponent skrevet i en tråd med en nøjagtighed på omkring 20ms, men hvis du ikke ved hvordan man instalerer et komponent hjælper det dig ikke meget.
Problemet er den tegner grafen samme antal gange. Du kan bare nøjes med at gentegne grafen 3-4 i sekundet i stedet for gange, men stadig adde alle de items til stringlisten alligevel. Jeg tror ikke det menneskelige øje opdager den store forskel. Grafen bliver fuldstændig identisk alligevel.
Godt spørgsmål. Der er mange faktorer der kan justere dette - eksempelvis hardware. Kommer du over i noget der overhaler hastigheden på din USB (som du benytter til måleren)? Måske er driveren til USB produktet ikke gearet til dine mange forespørgsler. Måske er computeren det kører på for lille? (cpu, ram, disk, etc).
Jeg mener at have læst i et andet forum om en, der skulle bruge en timer, som var mere nøjagtig end 1 mSek! Tror det var About.Delphi.com Svaret har jeg desværre ikke kunne finde igen, men det må da kunne lade sig gøre.
Interfacekortet jeg benytter + den medfølgende software, skulle kunne klare samples op til 1,2 kHz. Computeren er på 1,8 MHz, 256 Mbyte RAM, rigeligt fri harddisk plads osv.
hvis du bruger noget hardware (ja men forstå det rigtigt *g) som du sætter op til et bestemt antal samples i sekundet og læser dem ind vha en timer "ind imellem" så er det jo kortets klokke der bestemmer farten. Jeg har selv lavet et sådant program (vb6 i år 2000) og her satte jeg kortet op til 300 samples i sekundet, og fik en buffer fuld af gangen, som jeg så evaluerede, jeg testede på status i et loop og lige så snart bufferen var klar, sendte jeg en request afsted om en ny buffer og behandlede så bufferen som var færdig, det er ihvertilfælde en god og afprøvet metode. Bemærk at jeg var lige glad med gab imellem to buffere, det var lige meget, du kan jo tage højde for dét problem.
Hvad med GetTickCount? Den giver antallet af ms siden dagens start. Jeg ved ikke (og gider ikke slå op i VCL'en - beklager) om det er samme dims som TTimer kører på, men hvis ikke så kan du jo bruge den.
Jeg forestiller mig et loop á lá
while true do begin if (GetTimerTick mod 10) = 0 then begin // Kald en tråd der opdaterer graf så dette ikke sløver loopet // I alt fald bør du adskille dataopsamling og den grafiske del! end; Application.HandleMessages; // Så det ikke er busy waiting det hele Sleep(5); // Tag en lur ?? end;
Jeg har testet med timeren sat til 40 mSek., og det var også stabilt - men sikkert lige på grænsen. Jeg behøver ikke en opdateringstid på 1200 gange i sek., men 100 gange (10mSek.) skal jeg opnå. Hvilke andre realistiske muligheder mener du der er? Hvis det drejer sig om at indstallere en komponent, må jeg jo bare lære det.
Beklager jeg ikke lige fik besvaret dine kommentarer :)
nå, okay sidste try: du læser altså en måling ad gangen fra dit interface kort, ikke? Hvad med at sætte kortet op til at opsamle flere målinger og læse dem i blokke fx hver sek. Det kan faktisk være at det er den eneste chance for at lave det på en ordentlig måde. --nop
Jeg har desværre ikke mulighed for ændre i opsætningen af, hvordan Delphi kommunikerer med kortet. Alt dette er lagt fast fra fabrikantens side, så jeg kun får stillet en integer til rådighed, som indeholder den aflæste værdi. På en eller anden måde kan det sikkert lade sig gøre, men da jeg endnu ikke er en haj til Delphi, prøver jeg at udnytte det forarbejde, fabrikanten har lavet. Men tak for ideerne :-)
Jeg har leget lidt med tanken og mit busy-loop bør pakkes ind i en tråd. Så kan windows få lov til at kæmpe lidt med busy-waiting problematikken uden at de andre programmer går kolde.
Det er ikke sikkert at "GetTickCount mod 10" vil ramme noget der går op i 10. Jeg skitserer en lidt anden løsning nedenfor (bemærk, at jeg ikke tager højde for at tælleren nulstilles ved midnat):
var TickCount, NextTime : longint; begin NextTime := 0; while not Terminated do begin TickCount := GetTickCount; if TickCount > NextTime then begin // Hent data og send dem videre til den anden process. NextTime := TickCount + 10; // 10 ms end; // Måske skal der en sleep / HandleMessages ind her? end; end;
Den anden process må være i stand til at modtage alle de data som du kan sende, men og samtidig være i stand til at opdatere skærmen med jævne mellemrum. Her kan Borrisholts kbmTable sikkert være god. Jeg har ikke helt styr på opbygningen, men tror dog at ideen har potentiale...
procedure TTemperatureThread.Execute; begin fNextTemperature := 0; while not Terminated do begin fTickCount := GetTickCount; if fTickCount >= fNextTemperature then begin fTick := fTickCount; fA := random(100); fB := random(100); fC := random(100); fD := random(100); Synchronize(UpdateList); fNextTemperature := fTickCount + 10; end; Sleep(1); end; end;
procedure TTemperatureThread.UpdateList; begin fUpdateList.Add(format('Tick: %10d [%.3d, %.3d, %.3d, %.3d]',[fTick,fA,fB,fC,fD])); end;
-> hrc: Så har jeg fået dit eksempel op at køre. Jeg går ud fra, der skal ske et eller andet i ListBoxen, når der trykkes på btnStart. Det gør der ikke i mit eksempel!
Der tager du nu fejl. Det var kun for at indikere, at man bankede data over i en liste af en art (her brugte jeg TListBoksens TStringList), som tanken var, løbende skulle tømmes af en anden tråd/process. Her ville kbmTable være smart.
Jeg har to knapper på formen, en start og en stop og når du stopper kan du se, at jeg også sætter en Items.EndUpdate - væn dig i øvrigt til at bruge BeginUpdate og EndUpdate på lister (nedarvinger af TList). Det speeder det hele op en hel del, men listerne opdateres naturligvis ikke i mellemtiden. Mit brug her er lidt et hack. Den normale opsætning er at placere det i en try-finally:
lbData.Items.BeginUpdate; try // Gør et eller andet langvarigt finally lbData.Items.EndUpdate; end;
Bemærk, at vi er på grænsen mht. GetTickCOunt. Jeg kunne ikke komme længere ned end til ca. 10ms, mens stoney fik den ned på 2-8. Det hele afhænger af hvor meget, du stopper ind i loopet. Jeg brugte nogle Random'er, idet jeg antog, at de var ret sløvende.
-> hrc: vil det sige, jeg skal bruge en memtable? Det mest optimale vil selvfølgelig være, hvis det er noget, jeg kan flette sammen med det program jeg allerede har. Bortset fra det med opdateringstiden kører programmet nemlig upåklageligt, og så er det rimeligt overskueligt.
-> Stoney: Der sker en mærkbar forbedring af opdateringstiden, hvis jeg sletter et par linier i OnTimer event proceduren. Derfor har jeg, som du foreslåede, forsøgt at flytte alt, der har med opdateringen af kurven at gøre, over i en anden OnTimer event procedure, som kun opdateres 5 gange pr. sek. Dog fik jeg det aldrig til at køre, pga. noget med definering af Integer i :( Hvis jeg sender denne OnTimer event del af mit program, kan jeg så lokke dig til at flytte disse ikke så kritiske dele af proceduren over i en anden, som ikke opdateres så ofte? Det vil være fedt, da det muligvis er det, der skal til.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin sl.Free; end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
i : integer;
begin
min := 5000; max := -5000; begin if sl.Count <= 0 then for i := 0 to (strtoint(edit1.Text) * 100) - 1 do sl.Add('0'); end; sl.Add(floattostr(RandExt(71))); if (sl.Count > (strtoint(edit1.Text)*100)) then begin for i := 0 to (sl.Count -(strtoint(edit1.Text)*100)-1) do sl.Delete(i); end;
for i := 0 to sl.Count -1 do begin
if max < strtofloat(sl[i]) then max := strtofloat(sl[i]); if (min > strtofloat(sl[i])) and (strtofloat(sl[i]) <> 0) then min := strtofloat(sl[i]);
if savedialog1.Execute then chart1.SaveToBitmapFile(savedialog1.FileName); end;
procedure TForm1.Timer2Timer(Sender: TObject); var i : integer; begin chart1.SeriesList.Series[0].Clear; for i := 0 to sl.Count - 1 do begin chart1.Title.Text.Text := ' sidste ' +inttostr(sl.Count ) + ' målinger ' + 'Max: ' + floattostr(max) + ' Min: ' + floattostr(min); chart1.SeriesList.Series[0].AddY(strtofloat(sl[i]),'',clgreen); chart1.LeftAxis.Maximum := max + 0.1; chart1.LeftAxis.Minimum := min - 0.1; end; end;
->Stoney: det gav en lille forskel, men det var desvärre ikke nok (du skal nok fä nogen point for dit arbejde alligevel). Jeg holder spörgsmälet äbent, mens jeg grubler over, hvad der skal til.
Er du sikker på det ikke er din hardware. Hvis det er må du samle data op i "klumper".
Tstringlist er altså rimelig hurtig. Prøv selv at teste. Du angiver hvor mange items du vil have i edit1
procedure TForm1.Button1Click(Sender: TObject); var t : longword; sl : Tstringlist; i : integer; begin t := gettickcount; sl := Tstringlist.Create; begin for i := 0 to strtoint(edit1.Text) - 1 do sl.Add(inttostr((i))); end; showmessage('Det tog ' + inttostr(gettickcount-t) + ' milliesekunder'); sl.Free; end;
I klumper jo rundt i det :) du skulle tage at finde ud af hvordan man sætter den hw op ellers vil du alligevel aldrig få det til at køre på en win-platform for win tager ind imellem al tid til jeg ved ikke hvad, hvad hvis du sætter en cd i, så er dine data ikke tidsmæssigt gode nok ! --nop
det har jeg jo skrevet og du skrev osse et svar om at du ikke viste hvordan man programmerede kortet til klump-mode og at du kun fik en integer adgangen på en port. Det var nærmest for sjov da stoney skrev "klumper" --nop
->Stoney: jo, det gär forholdsvis hurtigt, men der er programmet ogsä temmeligt lille. När jeg i mit program tester, har jeg lagt märke til fölgende: Ved timer1 sat til 50 mSek. körer opdateringen fint indtil 9000 mälinger (450 sek.) i StringListen. Over 9000 gär det galt. Det er nok rigtig nok, som nop er inde pä, at der skal väre en buffer et eller andet sted, säledet at der stadigväk bliver opsamlet data, när Windows pludselig bruger al power til noget andet.
Jeg har ikke haft tid til at arbejde med projektet i lang tid, og det fär jeg heller ikke i när fremtid. Derfor mä det väre tid til at lukke spörgsmälet. Jeg har fundet ud af, at mälingerne kan lägges direkte til charten, i stedet for först at lägge dem i en StringList: Chart1.SeriesList.Series[0].AddY. Det sparer äbenbart en del tid, men er stadigväk ikke 100% stabilt.
Dem, som mener, de har fortjent point, melder sig lige med et svar...
Tak alle for rädene :)
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.