Avatar billede js_delphi Nybegynder
30. juli 2004 - 10:38 Der 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å).
Avatar billede borrisholt Novice
30. juli 2004 - 10:48 #1
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.

Jens B
Avatar billede js_delphi Nybegynder
30. juli 2004 - 10:49 #2
Det skal lige nævnes, at CPU'en ikke arbejder 100 %.
I øvrigt bruger jeg Delphi 7.
Avatar billede stoney Nybegynder
30. juli 2004 - 10:54 #3
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.

Stoney
Avatar billede kammeyer Nybegynder
30. juli 2004 - 10:54 #4
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).

Her du et link til måler produktet?
Avatar billede stoney Nybegynder
30. juli 2004 - 10:55 #5
Du kan bare nøjes med at gentegne grafen 3-4 i sekundet i stedet for gange,

skulle være

Du kan bare nøjes med at gentegne grafen 3-4 i sekundet i stedet for gange 100,

Stoney
Avatar billede borrisholt Novice
30. juli 2004 - 11:08 #6
Det ændere ikke noget på at du ikke kan gå under 50ms, grundet at en TTimer ikke er mere nøjagtig.

Jens B
Avatar billede js_delphi Nybegynder
30. juli 2004 - 12:58 #7
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.

-> Stoney: jeg prøver lige din ide af.
Avatar billede nop Nybegynder
30. juli 2004 - 13:10 #8
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.
Avatar billede borrisholt Novice
30. juli 2004 - 13:34 #9
js_delphi>> Jeg prøver lige igen :
Jeg fik ingen respons før !

Hvis dit problem er at få opdateret grafen, vil stoney's løsning virke fint.

Skal du have noget målt med en stører nøjagtighed end 50ms vil det ikke virke, fordi en TTimer har stadig kune en nøjagtighed på 50ms !

Skal du have noget gentaet med en frekvens stører end 1200 gange i sekundet må du gribe til yderligheder, og der med noget andet end en Ttimer !!!

Jens B
Avatar billede hrc Mester
30. juli 2004 - 13:51 #10
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;
Avatar billede js_delphi Nybegynder
30. juli 2004 - 14:05 #11
Hej Jens,

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 :)
Avatar billede nop Nybegynder
30. juli 2004 - 14:10 #12
er det ikke et eller andet hw du læser igennem, eller er det bare en com port?
Avatar billede nop Nybegynder
30. juli 2004 - 14:20 #13
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
Avatar billede js_delphi Nybegynder
30. juli 2004 - 15:01 #14
Hej 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 :-)

->hrc:
Det tester jeg af i løbet af dagen :)
Avatar billede hrc Mester
30. juli 2004 - 17:17 #15
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...
Avatar billede hrc Mester
30. juli 2004 - 18:10 #16
Lige en opdatering og nogle rettelser:

1. GetTickCount nulstilles ikke ved midnat. Den kører ca. 50 dage og starter så forfra.

2. Jeg kan ikke komme under 10ms. Kan ikke læse mig frem til om det er mindste "tick".

Jeg har prøvet med denne tråd:

unit UTemperatureThread;

interface

uses
  SysUtils, Classes, Windows;

type
  TTemperatureThread = class(TThread)
  private
    fA, fB, fC, fD : integer;
    fTickCount : longword;
    fNextTemperature : longword;
    fTick : longword;
    fUpdateList : TStrings;
  protected
  public
    constructor Create(aSuspended : boolean; aUpdateList : TStrings); reintroduce;
    procedure Execute; override;
    procedure UpdateList;
  end;

implementation

{ TTemperatureThread }

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;

constructor TTemperatureThread.Create(aSuspended: boolean; aUpdateList: TStrings);
begin
  fUpdateList := aUpdateList;
  FreeOnTerminate := true;
  inherited Create(aSuspended);
end;

end.

og programmet:

unit FMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, UTemperatureThread;

type
  TfrmMain = class(TForm)
    btnStart: TButton;
    btnStop: TButton;
    lbData: TListBox;
    procedure btnStartClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
  private
    fTemperatureThread : TTemperatureThread;
  public
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.btnStartClick(Sender: TObject);
begin
  lbData.Items.BeginUpdate;
  fTemperatureThread := TTemperatureThread.Create(false,lbData.Items);
end;

procedure TfrmMain.btnStopClick(Sender: TObject);
begin
  fTemperatureThread.Terminate;
  lbData.Items.EndUpdate;
end;

end.

Det kan du da prøve at lege lidt med.
Avatar billede stoney Nybegynder
30. juli 2004 - 18:28 #17
Gettickcount er vel det mest præcise windows kan præstere.

Og det er ikke præcist.

procedure TForm1.Button1Click(Sender: TObject);
var
  t : longword;
  ms : integer;

  begin

  t := GetTickCount;

  WHILE  (gettickcount - t) < (strtoint(edit1.Text) * 1000) DO

      begin

      ms :=  (gettickcount - t);

      end;

  showmessage('Der er gået ' + inttostr(ms) + ' milli sekunder' + #13#10 + 'en nøjagtighed på ' +(inttostr(strtoint(edit1.text)* 1000 -ms)) + ' milli sekunder');

  end;

Værdien du skriver i tedit er i sekunder altså 3 = 3 sekunder.

Den har en nøjagtigehed på 2-8 milli sekunder på mine tests.

Stoney
Avatar billede js_delphi Nybegynder
30. juli 2004 - 18:41 #18
Jeg er nødt til at forlade PC'en et par timer. Når jeg har testet forslaget, vender jeg tilbage. Det bliver rimelig spændende.
Avatar billede js_delphi Nybegynder
30. juli 2004 - 22:19 #19
-> 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!
Avatar billede hrc Mester
31. juli 2004 - 06:52 #20
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.
Avatar billede js_delphi Nybegynder
31. juli 2004 - 11:43 #21
-> 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.
Avatar billede stoney Nybegynder
01. august 2004 - 15:00 #22
Jeg retter det til i morgen mandag.
Min kode er på jobbet.

Stoney
Avatar billede stoney Nybegynder
02. august 2004 - 12:27 #23
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, TeEngine, Series, ExtCtrls, TeeProcs, Chart;

type
  TForm1 = class(TForm)
    Chart1: TChart;
    Button2: TButton;
    Series1: TFastLineSeries;
    Button1: TButton;
    Button3: TButton;
    Button4: TButton;
    Timer1: TTimer;
    SaveDialog1: TSaveDialog;
    Button5: TButton;
    OpenDialog1: TOpenDialog;
    Edit1: TEdit;
    Timer2: TTimer;
    procedure Button2Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Timer1Timer(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  sl : Tstringlist;
 
  min,max : real;

implementation

{$R *.dfm}

function RandExt(Max: Integer): Real;
var
  tmp: int64;
begin
  tmp := Random(72) + 67 ;
  Result := tmp / 72 + 67;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
i : integer;

begin
if opendialog1.Execute then
chart1.SeriesList.Series[0].Clear;
min := 5000;
max := -5000;
sl.Clear;
sl.LoadFromFile(opendialog1.FileName);


for i := 0 to sl.Count -1 do
  begin


  if max < strtofloat(sl[i]) then
  max := strtofloat(sl[i]);
    if min > strtofloat(sl[i]) then
  min := strtofloat(sl[i]);

  chart1.SeriesList.Series[0].AddY(strtofloat(sl[i]),'',clgreen);




  end;
  chart1.Title.Text.Text := inttostr(sl.Count) + ' målinger ' + 'Max: ' + floattostr(max) + '  Min: ' + floattostr(min);

  chart1.LeftAxis.Maximum := max + 0.1;
  chart1.LeftAxis.Minimum := min - 0.1;

end;

procedure TForm1.Button1Click(Sender: TObject);
begin
chart1.SeriesList.Series[0].Clear;
chart1.Title.Text.Text :=  'Temperatur måling';

end;

procedure TForm1.Button3Click(Sender: TObject);
begin

button1.Enabled := false;
button2.Enabled := false;
button3.Enabled := false;
button4.Enabled := true;
button5.Enabled := false;
timer1.Enabled := true;
timer2.Enabled := true;
end;


procedure TForm1.Button4Click(Sender: TObject);
begin

button1.Enabled := true;
button2.Enabled := true;
button3.Enabled := true;
button4.Enabled := false;
button5.Enabled := true;
timer1.Enabled := false;
timer2.Enabled := false;
savedialog1.DefaultExt := '*.txt';
savedialog1.Filter := 'txt|*.txt';

savedialog1.FileName :='Måling_'+ datetostr(now) + '_' + StringReplace(timetostr(now), ':', '', [rfReplaceAll]);;
if savedialog1.Execute then

sl.SaveToFile(savedialog1.FileName);


sl.Clear;
antalmaaling := 0;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
randomize;
sl := Tstringlist.create;
antalmaaling := 0;
end;

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]);






  end;


end;

procedure TForm1.Button5Click(Sender: TObject);
begin
savedialog1.DefaultExt := '*.bmp';
savedialog1.Filter := 'bmp|*.bmp';
savedialog1.FileName :='Måling_'+ datetostr(now) + '_' + StringReplace(timetostr(now), ':', '', [rfReplaceAll]);;

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;

end.


Stoney
Avatar billede js_delphi Nybegynder
02. august 2004 - 13:54 #24
Tak for det, jeg tester det lige.
Avatar billede js_delphi Nybegynder
03. august 2004 - 13:44 #25
->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.
Avatar billede stoney Nybegynder
03. august 2004 - 14:15 #26
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;

Stoney
Avatar billede nop Nybegynder
03. august 2004 - 15:55 #27
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
Avatar billede js_delphi Nybegynder
03. august 2004 - 16:19 #28
-> nop: kan du pröve lidt mere präcist at beskrive dine ideer mht. hw?
Avatar billede nop Nybegynder
03. august 2004 - 16:22 #29
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
Avatar billede js_delphi Nybegynder
03. august 2004 - 16:38 #30
->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.
Avatar billede hrc Mester
30. august 2004 - 16:46 #31
Jeg fandt lige dette link:

http://www.delphifaq.com/fq/q2113.shtml
Avatar billede js_delphi Nybegynder
26. november 2004 - 20:08 #32
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 :)
Avatar billede Ny bruger Nybegynder

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.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester