Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Timing-Event im 10ms-Bereich (https://www.delphipraxis.net/136361-timing-event-im-10ms-bereich.html)

MaxDelphi 29. Jun 2009 10:33


Timing-Event im 10ms-Bereich
 
Hallo Forum,

ich hab folgendes Problem:
Ich hätte gerne Ereignisse im 10ms (oder kleiner) - Takt, die mir die CPU nicht verstopfen.

Habe hier schon etliches über genaue Zeitmessung, usw gelesen aber nicht das geeignete gefunden.

Was ich bisher getestet habe:
1.HighPrecision-Timer von TMS -> hält die 10ms recht gut ein, aber CPU-Last steigt auf 100%, da er die CPU-Ticks zählt.
2.TTimer mit Intervall 10ms liefert ca. alle 15-16ms einen Event auch wenn ich noch kleinere Zeiten einstelle.
3.Idee 2 TTimer mit 20ms um 10ms versetzt anstarten. Den Versatz habe ich über TMS HighprecisionDelay realisiert. -> 2 Ereignisse im 15ms-Raster + 1 Ereignis im 0ms-Raster.

Wie habe ich das herausgefunden:
Mit einem Button habe ich den Timer angestoßen, mit einem zweiten wieder gestoppt. Während der Timer lief habe ich mir die aktuellen Zeiten (now) und deren Differenzen in einen String schreiben lassen. Wenn gestoppt wurde habe ich den String in einer Memo ausgegeben. Auch den Now an sich habe ich überprüft indem ich am Anfang und am Ende der Routine die Zeit gemessen habe und die Differenz ausgeben habe.

Ich weiß, daß ein solches Verhalten machbar ist, denn ich habe es in NI LabView überprüft. -> 10ms Taktzeit Prozessorlast <5%

4.Nachdem ich das mit LabView hinbekommen habe, hab ich mir eine Delphi-DLL gebaut die eine WM_USER-Message sendet. Diese DLL habe ich in LabView verwendet um mir genaue Timing-Events zu senden. Die LabView-Routine habe ich wiederum als DLL abgebunden und wieder in Delphi aufgerufen. Ergebnis war wie bei 2.

Anbei der Code für 2.) mit drei unterschiedlichen Varianten der Zeitermittelung zum Test.

Delphi-Quellcode:
unit TimerTest;

interface

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

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
    Msg   : string;
    OldTime   : TDateTime;
    Freq   : Int64;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

FUNCTION TimeStamp:Int64;
asm
   rdtsc
end;

FUNCTION TimeStampFrequency:int64;
var oldpriority:word; q0,q1,qf,ts0,ts1:int64; seconds:extended;
begin
   oldpriority:=GetThreadPriority(GetCurrentThread);
   SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_TIME_CRITICAL);
   QueryPerformanceCounter(q0);
   ts0:=TimeStamp;
   Sleep(200);
   ts1:=TimeStamp;
   QueryPerformanceCounter(q1);
   SetThreadPriority(GetCurrentThread,oldpriority);
   QueryPerformanceFrequency(qf);
   seconds:=(q1-q0)/qf*1000;
   result:=Trunc((ts1-ts0)/seconds+0.5);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
   CmdStart, CmdEnd   : TDateTime;
   i, j   : Integer;
begin
//   CmdStart   := Now * 86400000;
//   CmdStart   := GetTickCount();
   CmdStart   := TimeStamp;
   j   := 1;
   for i := 0 to 100 do
      j   := i;
//   Msg   := Msg + Format('%d %8.8fms ',[j,CmdStart-OldTime]);
   Msg   := Msg + Format('%d %8.8fms ',[j,(CmdStart-OldTime)/Freq]);
//   CmdEnd   := Now * 86400000;
//   CmdEnd   := GetTickCount();
   CmdEnd   := TimeStamp;
//   Msg   := Msg + Format('%8.8fms',[CmdEnd-CmdStart])+#13#10;
   Msg   := Msg + Format('%8.8fms',[(CmdEnd-CmdStart)/Freq])+#13#10;
   OldTime   := CmdStart
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
   Memo1.Clear;
   Msg   := '';
//   OldTime   := Now * 86400000;
//   OldTime   := GetTickCount();
   OldTime   := TimeStamp;
   Timer1.Interval   := 10;
   Timer1.Enabled   := True;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
   Timer1.Enabled   := False;
   Memo1.Text   := Msg;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   Freq   := TimeStampFrequency;
end;

end.
Die Funktionen TimeStamp und TimeStampFrequency sind aus Eurem Beitrag "exakte Zeitmessungen auf Multiprozessoren" entnommen.


Hat irgendjemand eine Idee wie ich Delphi zu solchen Timing-Events überreden kann ohne die CPU zu blockieren?

Für Eure Hilfe Danke im voraus.

Reinhard Kern 29. Jun 2009 13:05

Re: Timing-Event im 10ms-Bereich
 
Zitat:

Zitat von MaxDelphi
...
Ich hätte gerne Ereignisse im 10ms (oder kleiner) - Takt, die mir die CPU nicht verstopfen.
...

Hallo,

die Frage taucht immer und immer wieder auf, und die exakte Antwort kann wie immer nur heissen: Desktopsysteme wie Windows oder Linux sind keine Echtzeitsysteme, zuverlässig funktionieren wird das NIEMALS.

Damit es annähernd funktioniert, also fast immer mit seltenen Ausfällen, müsstest du wahrscheinlich in die Treiberprogrammierung einsteigen, auf Anwendungsebene wird das nicht gehen. Ich nehem an, Labview macht es auch so, kann mich aber täuschen.

Wenn du derzeit auf eine untere Grenze bei 15 ms stösst, so kann auch ein schnellerer Rechner helfen, bei doppelter Ausführungsgeschwindigkeit solltest du dann unter 10 ms kommen, aber das ist keine echte Lösung.

Gruss Reinhard

himitsu 29. Jun 2009 13:12

Re: Timing-Event im 10ms-Bereich
 
Zitat:

Zitat von MaxDelphi
Wie habe ich das herausgefunden:
Mit einem Button habe ich den Timer angestoßen, mit einem zweiten wieder gestoppt. Während der Timer lief habe ich mir die aktuellen Zeiten (now) und deren Differenzen in einen String schreiben lassen. Wenn gestoppt wurde habe ich den String in einer Memo ausgegeben. Auch den Now an sich habe ich überprüft indem ich am Anfang und am Ende der Routine die Zeit gemessen habe und die Differenz ausgeben habe.

nimm mal kein NOW zur Zeitmessung, denn dieses hat schonmal nur Genauigkeiten in dem gemessenem Bereich (8 bzw. 16 ms),
also kleinere Einheiten kannst du damit eh nicht genau messen.
Auch GetTickCount kann bei solch kleinen Messeinheiten nicht genutzt werden.

MaxDelphi 29. Jun 2009 13:19

Re: Timing-Event im 10ms-Bereich
 
@ Reinhard,

danke für die Antwort.

Wenn die Frage immer wieder auftaucht, hat dann noch keiner so einen Mini-Trieber geschrieben der diese Funktion besitzt oder einen Link parat wie man so einen Trieber schreiben kann??

Könnte man eventuell über den DependencyWalker oder ähnliche Programme herausschnüffeln wie das geht?

Ich wäre für jede Hilfe dankbar.

@ himitsu,

danke für den Hinweis, aber die Zeitmessung in diesem Fall dient nur der Überprüfung der Aufgabe Timing-Events im 10ms-Raster.

Wie du im Quell-Code unschwer erkennen kannst habe ich 3 verschiedene Arten der Zeitmessung ausprobiert.

Reinhard Kern 29. Jun 2009 13:20

Re: Timing-Event im 10ms-Bereich
 
Hallo,

lies mal über WaitForSingleObject. Dies bewirkt eine Wiederaufnahme des Threads unmittelbar, während eine WM_TIMER-Message nur in die Warteschleife eingefügt wird und dann IRGENDWANN abgearbeitet.

Gruss Reinhard

jfheins 29. Jun 2009 13:24

Re: Timing-Event im 10ms-Bereich
 
Zitat:

Zitat von Reinhard Kern
die Frage taucht immer und immer wieder auf, und die exakte Antwort kann wie immer nur heissen: Desktopsysteme wie Windows oder Linux sind keine Echtzeitsysteme, zuverlässig funktionieren wird das NIEMALS.

Richtig :stupid:
Zitat:

Wenn du derzeit auf eine untere Grenze bei 15 ms stösst, so kann auch ein schnellerer Rechner helfen, bei doppelter Ausführungsgeschwindigkeit solltest du dann unter 10 ms kommen, aber das ist keine echte Lösung.
Mööööp - falsch :mrgreen:

Windows als nicht-Echtzeit-System teilt die Zeit in viele, kleine Abschnitte ein. Dann lässt es dein Programm einen Abschnitt lang rechnen und danach ist Schluss, andere Programme kommen dran. (Präemtives Multitasking lässt grüßen) Nach ca. 15ms kommt dein Programm wieder dran.

Die einzige Lösung, unter Windows halbwegs genau diese Zeit zu messen ist das zählen der Prozessorzyklen (vorzugsweise mit Priorität "Echtzeit") was aber - wie du bereits bemerkt hast - 100% CPU Last verursacht.

Ein Schnellerer Prozessor würde legendlich dazu führen, dass du in deiner Zeitspanne mehr abarbeiten kannst, diese 15ms werden dadurch aber iirc nicht wesentlich beinflusst.

jbaertle 29. Jun 2009 13:44

Re: Timing-Event im 10ms-Bereich
 
Hallo,
je nach Aufwand, den man treiben muss, kann man z.B. Microsoft Windows XP Embedded verwenden. Dies ist ein auf den ersten Blick bekanntes (und funktionales) Windows XP, welches sich soweit "aufrüsten" lässt, dass es üblichen Anforderungen an Echtzeitsystemen (zumindest im Bereich von Werkzeugmaschinen) gerecht wird. Wir verwenden beispielsweise die Sinumerik 840 Di sl, wo wir definierte 6 ms Zykluszeit zur Verfügung haben. Ähnliches gibt es auch z.B. von Beckhoff.
Das ist aber keine Spielerei sondern geht schwer ins Geld, d.h. man macht es nur, wenn man muss.
MfG

MaxDelphi 29. Jun 2009 14:43

Re: Timing-Event im 10ms-Bereich
 
@ jfheins,

Zitat:

Windows als nicht-Echtzeit-System teilt die Zeit in viele, kleine Abschnitte ein. Dann lässt es dein Programm einen Abschnitt lang rechnen und danach ist Schluss, andere Programme kommen dran. (Präemtives Multitasking lässt grüßen) Nach ca. 15ms kommt dein Programm wieder dran.
das scheint mir eine sehr gute Erklärung für das Problem zu sein!
Aber wäre das nicht ein Ansatz um das Problem zu lösen. Kommt man vielleicht programmiertechnisch an die Einteilung der Zeitscheibe heran, so daß man sie zumindestens zeitweise umstellen kann, z.B. alle 7,5ms ist mein Programm an der Reihe.

Hat da schon mal jemand was gemacht? Wenn ja wie geht das?

himitsu 29. Jun 2009 15:50

Re: Timing-Event im 10ms-Bereich
 
genau timen kann man damit nichts, man könnte nur die Process- und Thread-Priorität erhöhen, dann kommt es theoretisch öfters mal in den Bearbeitungsprozess rein, aber wie of und wie lange Windows sich dann mit deinem Programm befaßt, ist damit nicht bestimmt.

jfheins 29. Jun 2009 16:03

Re: Timing-Event im 10ms-Bereich
 
Verwende Windows 3.x - das kann sowas ;)

Unter Win 3.x wird noch kooperatives "Multitasking" betriben, das heißt deine Anwendung hat alle Zeit die es will und wird nicht gestört :mrgreen:

Ansonsten: Oben wurde ein modifiziertes WinXP angesprochen, oder verwende einen Mikrocontroller - da haste auch nen Prozessor für dich alleine.

Bitte nicht angegriffen fühlen - aber soweit ich weis wirst du unter einem normalen WinXP (&Vista&Win7) nicht in diesen Genauigkeitsbereich kommen.

MaxDelphi 29. Jun 2009 17:27

Re: Timing-Event im 10ms-Bereich
 
Hallo Leute,

nochmals Danke für eure Hilfe. Oftmals sind es doch die Anregungen der anderen die einen wieder frei denken lassen, so daß einem neue Suchbegriffe einfallen und einen zum Ergebnis führen. Bei mir waren das "time slice" -> "win32priorityseparation" -> "quantum" -> "timeBeginPeriod" -> und schon war ich wieder bei euch gelandet und zwar:

Delphi für Win32
Schneller Timer gesucht
Im 11. Beitrag hat Gandalfus den Link zu einem guten Tutorial gepostet, womit mir gelungen ist was ich gesucht habe -> Timing-Events <= 10ms ohne Prozessorlast. Wobei die Genauigkeit der 10ms nicht so bedeutend sind, sondern ich wollte auf ca. 100Hz kommen.

Ich hänge ein kleines Programm an, das dem besagten Tutorial entnommen ist und von mir modifiziert wurde. Es enthält wieder Now und nun glaube ich es auch: Now ist nicht besonders exakt!! Aber wenn man die Events eine Weile auflaufen läßt und die verstrichen ms von Now durch die Anzahl der Durchläufe teilt kommt man wieder auf ca. 10ms.

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, mmsystem, StdCtrls; //!!!!  mmsystem einbinden (für timegettime)
type
   TMsgContainer   = class
   public
      Msg   : string;
   end;

  TThreadTimer = class(TThread)
  private
    TheMsg: TMsgContainer;
    Zeit: int64;
    t0   : TDateTime;
    Cnt   : Int64;
  published
    procedure Execute; override;
    procedure OnTimer;
    constructor CreateMitCanvas(AMsg: TMsgContainer); virtual;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    ThreadTimer: TThreadTimer;
    Timerid: integer;
    MyMsg   : TMsgContainer;
  end;

var
  Form1: TForm1;

implementation
{$R *.dfm}

//Dem constructor wird eine zeichenfläche übergeben,
//damit klar ist wohin gezeichnet werden soll
constructor TThreadTimer.CreateMitCanvas(AMsg: TMsgContainer);
begin
  inherited create(true);
  TheMsg := AMsg;
  Zeit := Timegettime ;         //Die Startzeit wird ermittelt
  t0   := Now;
  Cnt   := 0;
  resume;
end;

procedure TThreadTimer.Execute;
begin
  while not Terminated do
  begin
    sleep(1);
    OnTimer;
  end;
end;

procedure TThreadTimer.OnTimer;
Var
   NewTime, Delay   : int64;
   ElapsedMS   : TDateTime;
begin
   NewTime   := timegettime;
   Delay   := NewTime-Zeit;
   ElapsedMS   := (Now-t0)*86400000;
  if NewTime-Zeit>=9 then //Jeweil nach einer Sekunde
  begin
   Inc(Cnt);
    TheMsg.Msg   := TheMsg.Msg   + Format('%.3d %8.3fms %d',[Cnt,ElapsedMS,delay]) + #13#10;
    zeit:= NewTime; //neue Start Zeit ermitteln
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   MyMsg   := TMsgContainer.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  MyMsg.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
   MyMsg.Msg   := '';
  ThreadTimer := TThreadTimer.CreateMitCanvas(MyMsg);
  Button1.Enabled   := False;
  Button2.Enabled   := True;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
   ThreadTimer.DoTerminate;
  ThreadTimer.free; //Timer freigeben
  memo1.Text   := MyMsg.Msg;
  Button2.Enabled   := False;
  Button1.Enabled   := True;
end;

end.
Damit sehe ich das Problem als gelöst an.
Nochmals vielen Dank für eure Mühe.

Luckie 29. Jun 2009 19:02

Re: Timing-Event im 10ms-Bereich
 
Mal was anderes. Wozu brauchst du so eine geringe Auflösung? Eventuell gibt es da noch eine bessere Lösung. allgemein ist es immer schelcht einen Zustand immer wieder abzufragen. Besser ist es sich über einen Zustand mittels eines Ereignisses benachrichten zu lassen. Nicht umsonst wurde ja auch die Türklingel erfunden. ;)

MaxDelphi 29. Jun 2009 19:32

Re: Timing-Event im 10ms-Bereich
 
Hallo Luckie,

wenn du noch eine ereignisgesteuerte Lösung hast dann her damit, schau ich mir gerne an. Allerdings müssen die Ereignisse <= 10 ms ankommen und die CPU darf nicht ausgelastet werden; dieses Verhalten hatte ich erst im letzten Beispiel gesehen.

Des weiteren soll mir dieser Timer bei einer messtechnischen Anwendung helfen. Ich bekomme Daten aus verschiedenen Quellen geliefert, die sich hardwareseitig nicht synchronisieren lassen. Einerseits von Motorreglern (grobe Werte aber hohe Taktrate) andererseits Messdaten von einem Messverstärker (genaue Werte, langsame Taktrate). Die Werte lassen ich in ein gemeinsames Datenfeld laufen und hole sie mir dann "Quasi-Synchron" mit dem Timer ab. Und hier möchte ich auf ca. 100Hz kommen, damit ich dem Motorregler auch noch sagen kann wo es langgeht.

Die Idee Signale direkt vom Messverstärker in den Motorregler zu packen hatte ich auch schon allerdings ist der dort verbaute AD-Wandler zu schlecht. Es gibt auch noch weitere Gründe dagegen, so daß ich auf ein Softwaregeführte Variante zurückgreifen muß.

Ansonsten wünsche ich Euch allen noch einen schönen geruhsamen Abend.

himitsu 29. Jun 2009 19:42

Re: Timing-Event im 10ms-Bereich
 
Da wäre hier ein Microcontroler angebrachter, welcher die Ein- und Ausgangswerte mit passend seuern kann und diesen könnte man dann auch vom PC aus ansteuern ... zwar nicht alle 10ms (obwohl dieses auch möglich wäre).

Ein µC könnte 100 Hz wesendlich leichter einhalten
und dazu kommt noch, daß durch andere Umstände der PC auch mal "blockiert"/ausgebremmst sein kann und dann selber die 100 Hz garnicht mehr hinbekommt.

Falls die Messwerte dennoch für 100 Hz im PC auftauchen sollte, könnte der µC diese Werte auch mal zwischenspeichern und ich (un)regelmäßigen Abständen vom PC abgefragt bekommen.

hathor 30. Jun 2009 00:17

Re: Timing-Event im 10ms-Bereich
 
Liste der Anhänge anzeigen (Anzahl: 2)
Mit QPF-QPC sind auch 0,1 msec möglich:

himitsu 30. Jun 2009 08:13

Re: Timing-Event im 10ms-Bereich
 
Zitat:

Zitat von hathor
Mit QPF-QPC sind auch 0,1 msec möglich:

Allerdings bei nahezu 100% CPU-Last, was man aber nicht wollte

(ok, bei mir insgesammt nur knapp 50%, da 2 CPUs)

dmdjt 24. Jul 2009 22:12

Re: Timing-Event im 10ms-Bereich
 
Hab zu dem Timerproblem eine kleine Frage:

Kann man eigentlich nicht irgendewelche Dinge verwenden, die so oder so mit hoher Taktrate laufen und echt exakt sein müssen? Also zb das man Irgendwas von der Soundkarte einen Event auslösen läßt?

Ich weiß nur, dass es bei Softwaresynthesyzer ja auch irgendwie gehen muss wenn die ein live ein Geräusch produzieren können.

himitsu 24. Jul 2009 22:39

Re: Timing-Event im 10ms-Bereich
 
Zitat:

Zitat von dmdjt
und echt exakt sein müssen?

ist einfach nicht möglich, da immer irgendwas dazwischenfunken kann.

man kann zwar einem Prozess eine sehr hohe Priorität verpassen > Stichwort: critical time <
aber dann kann immernoch ein interrupt oder irgendelche Hardware dazwischenfunken
und zusätzlich ist der restliche PC nicht mehr bediehnbar.

Zitat:

Zitat von dmdjt
Ich weiß nur, dass es bei Softwaresynthesyzer ja auch irgendwie gehen muss wenn die ein live ein Geräusch produzieren können.

die produzieren mit Sicherheit kleine Soundhäppchen und schicken diese an die Soundkarte.
es wird bestimmt nicht der Soundausgang direkt angesteuert > das würde ja zu Knacken anfangen, sobald auch nur eine kurze Pause auftritt.

Luckie 25. Jul 2009 00:32

Re: Timing-Event im 10ms-Bereich
 
Zitat:

Zitat von MaxDelphi
wenn du noch eine ereignisgesteuerte Lösung hast dann her damit

Zu was denn? Du hast uns ja dein Problem nicht geschildert, sondern nur Lösungen genannt, die dich nicht befriedeigen.

@Reinhard Kern: Unter Windows NT hat der Timer eine minaml Auflösung von 15 ms. das hat mit der Geschwindigkeit der CPU nichts zu tun. Hinzukommt, dass die Timernachrichten im System eine sehr geringe Priorität haben, also schon mal etwas länger in der nachrichtenschlange stehen können. Hinzukommt, dass jeder Prozess eine Zeitscheibe von 20 ms zur CPU Nutzung zu gewiesen bekommt. Prozesse mit einer höheren Priorität kommen entsprechend öfters dran. Über den genauen Scheduling Algorithmus macht Mictrosoft keine genauen Angabewn, damit die Entwickler in ihren Anwendungen keine schmutzigen Tricks benutzen und dann nmit der nächsten Windows Version auf die Nase fallen, da es Microsoft sich vorbehält den Scheduling Algorithmus zu ändern, um die Systemperformance beständig zu verbessern.


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:17 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz