Recent

Author Topic: What happens when the TTimer interval is < than it takes to run its own code?  (Read 7769 times)

Alzibiff

  • Newbie
  • Posts: 3
I am returning to visual Pascal some 20 years since I used it last so things are a wee bit rusty...
I am writing a program to control a solenoid - when the solenoid is ON, it allows water to flow. When the solenoid is OFF, the water stops flowing. Why? I want to set up a sort of dripping tap and control the ON and OFF times of the solenoid to vary the size and frequency of the drips.

I have sorted out the code to switch the solenoid on and off from my RPi using one of its GPIO pins so I am making progress but the rest is making my brain hurt. (I have three solenoids to control and make three dripping taps).

I have a button which will toggle the dripping on and off and was thinking that a TTimer object would do the job with the OnTimer code going something like this ...

Switch solenoid ON
delay to generate a drip
turn solenoid OFF
delay to generate an interval between drips (end of OnTimer code, back to the start)

That way I get a drip every time that the timer ticks ... yes?

I want to vary both delay periods by taking values from a couple of edit boxes - that is, the time that it takes the TTimer to tick is going to change. What value do I set for the TTimer.interval?

Can I simply have a value less than the total of the combined delays - say 1ms -  so that I get a basic repeat..until false loop for my ONTimer code? In short, what I want to do is enable a timer so that it tick tick ticks away continuously repeating code till I tell it to stop by clicking a button which will disable it.


All assistance gratefully received,

Alan

« Last Edit: August 30, 2015, 08:29:08 pm by Alzibiff »

derek.john.evans

  • Guest
TTimer is somewhat inaccurate and the call back uses the main thread. TFPTimer also synchronizes with the main thread.

What about running time critical threads?

derek.john.evans

  • Guest
Quote
What happens when the TTimer interval is less time than it takes to run its own

I just had a look. From what I can tell, a timer with interval 500ms, will still tick away at 500ms even if the timer event takes 400ms.  If the event takes 1000ms, then the timer will slow to 1000ms.
ie: No timer events will "build up", and no-multiple timer calls will happen, since they are sent in the main thread.

So, yes, you can have a short timer, and then check LclIntf.GetTickCount64 to get the actual time. Which is what a lot of games do.

I guess, the issue is, TTimer runs in the main thread, which is effected by all GUI processing. So, how critical are these events?
« Last Edit: August 30, 2015, 03:54:24 pm by derek.john.evans »

Alzibiff

  • Newbie
  • Posts: 3
link=topic=29504.msg186515#msg186515 date=1440942709]
Quote
I guess, the issue is, TTimer runs in the main thread, which is effected by all GUI processing. So, how critical are these events?

That is good news (for me).
The accuracy of the timer is not so critical in my application but it needs to be as repeatable as possible from one call to the next.

I understand the point which you are making about the rest of the GUI processing affecting timer accuracy and although this seems that it COULD be a problem, my program will be doing the same tasks from call to call. That is, the GUI processing time should be more or less constant throughout the run of my program.

Of course, to confirm the above, I will just have to  suck it and see I guess! I'm grateful for your response though as I can continue to develop my application with a wee bit more confidence before the rest of my electronic components arrive and I get a chance to try things out experimentally.

One last question - what is "LclIntf.GetTickCount64" exactly and how do I check it?

Thank you,

ALan




derek.john.evans

  • Guest
The function GetTickCount64 is in the unit LclIntf.

Basically it maps to the Win32 GetTickCount function:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724408%28v=vs.85%29.aspx

It returns milliseconds and is 10-16ms accurate. (according to Microsoft).

It just means, you use a timer to monitor your taps, but you should use GetTickCount to determine if they should switch on/off since it is the real time. TTimer is an event interval which it tries to make good on.

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Here is a little test program. I have noticed that there is a minimum interval that TTimer is able to run, and that seems to be about 15. Anything less than interval 15 is redundant.

Code: [Select]
type

  { TForm1 }

  TForm1 = class(TForm)
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    delay, second: TTime;
  public
  end;

var Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  second:=encodetime(0, 0, 1, 0);
  delay:=Now+second;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  tag:=tag+1;
  if now>delay then begin
    delay:=delay+second;
    caption:=inttostr(tag);
    tag:=0;
  end;
end;
It keeps printing 64 ticks per second, when i have TTimer interval set to 1. 1000/64 = 15,625.

Anyway, for higher precision you need another timer (like some suggest Epictimer), or TThread with sleep(1). Without it a thread will max a cpu core, which is something TTimer will never do.

Also for TTimer a good strategy is to keep it running at the minimum interval at all times, never stopping it. Just handle all the timings with same code, and use boolean states determining if they need to do something or not.

Alzibiff

  • Newbie
  • Posts: 3
Anyway, for higher precision you need another timer (like some suggest Epictimer), or TThread with sleep(1). Without it a thread will max a cpu core, which is something TTimer will never do.

Also for TTimer a good strategy is to keep it running at the minimum interval at all times, never stopping it. Just handle all the timings with same code, and use boolean states determining if they need to do something or not.

Hmmm - that makes a lot of sense but sheesh - things have changed since my days of programming with the first version of Delphi!
I like the TThread idea and although I have had a quick scan at the Wiki to try and find out how to implement a thread which I can just use exclusively for my timer code, first impressions are that I am going to need more help to implement it. Being totally honest I am also confused as to where  sleep(1)  comes into the equation - can I simply not start a thread and stop a thread when I want to use one?

Alan

GetMem

  • Hero Member
  • *****
  • Posts: 3508
@Alzibiff
Here you go:
Code: [Select]
unit uThreadTimer;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, lclintf;

type
  TOnTimer = procedure(Sender: TObject) of object;

  { TThreadTimer }

  TThreadTimer = class(TThread)
  private
    FTime: QWORD;
    FInterval: Cardinal;
    FOnTimer: TOnTimer;
    FEnabled: Boolean;
    procedure DoOnTimer;
  protected
    procedure Execute; override;
  public
    property OnTimer: TOnTimer read FOnTimer write FOnTimer;
    property Interval: Cardinal read FInterval write FInterval;
    property Enabled: Boolean read FEnabled write FEnabled;
    procedure StopTimer;
    procedure StartTimer;
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
  end;


implementation

constructor TThreadTimer.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  FInterval := 1000;
  FreeOnTerminate := True;
  FEnabled := False;
end;

destructor TThreadTimer.Destroy;
begin
  //
  inherited Destroy;
end;

procedure TThreadTimer.DoOnTimer;
begin
  if Assigned(FOnTimer) then
    FOnTimer(Self);
end;

procedure TThreadTimer.Execute;
begin
  while not Terminated do
  begin
    Sleep(1);
    if (GetTickCount64 - FTime > FInterval) and (FEnabled) then
    begin
      FTime := GetTickCount64;
      Synchronize(@DoOnTimer);
    end;
  end;
end;

procedure TThreadTimer.StopTimer;
begin
  FEnabled := False;
end;

procedure TThreadTimer.StartTimer;
begin
  FTime := GetTickCount64;
  FEnabled := True;
  if Self.Suspended then
    Start;
end;
end.

Use it this way:
Code: [Select]
//...
 TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure OnTimer(Sender: TObject);
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation
uses uThreadTimer;
{$R *.lfm}

{ TForm1 }

var
  ThreadTimer: TThreadTimer;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ThreadTimer := TThreadTimer.Create(True);
  ThreadTimer.Interval := 1000;
  ThreadTimer.OnTimer := @OnTimer;
  ThreadTimer.StartTimer;
end;

procedure TForm1.OnTimer(Sender: TObject);
begin
  ShowMessage('ok');
end;             

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  ThreadTimer.StopTimer;
  ThreadTimer.Terminate;
end;

//... 

Please note:
1. The minimal interval is 15-20 ms. Under windows you can replace GetTickCount64 with timeGetTime(you should import this function, see microsoft documentation). This way you can get down to 5 ms. For even smaller intervals use QueryPerformanceCounter ~ 1 ms. Under linux, osx  see the clock_gettime function.
2. Synchronize will block your worker thread. If the code under Synchronize/OnTimer is time intensive, you should use queues or other asynchronous updating methods(fpc 3.1.1 needed for queues).

derek.john.evans

  • Guest
That looks like the same thread style implementation as fpTimer.TFPTimer

*EDIT* Actually, dont use fpTimer. It seems to use Now() for time.
Code: [Select]
function _GetTickCount: Cardinal;
begin
  Result := Cardinal(Trunc(Now * 24 * 60 * 60 * 1000));
end; 

Maybe Now() is better than what I thought?
« Last Edit: September 01, 2015, 09:59:17 am by derek.john.evans »

GetMem

  • Hero Member
  • *****
  • Posts: 3508
Quote
@derek.john.evans
That looks like the same thread style implementation as fpTimer.TFPTimer
It's similar but not the same. Please read this: http://forum.lazarus.freepascal.org/index.php/topic,27245.0.html for more info.

GetMem

  • Hero Member
  • *****
  • Posts: 3508
Re: What happens when the TTimer interval is < than it takes to run its own code?
« Reply #10 on: September 01, 2015, 10:05:06 am »
Quote
@derek.john.evans
Maybe Now() is better than what I thought?
No it's not. Definitely not suitable for threads. That's why fpTimer is a no go.

Thaddy

  • Hero Member
  • *****
  • Posts: 9184
Re: What happens when the TTimer interval is < than it takes to run its own code?
« Reply #11 on: September 01, 2015, 10:25:14 am »
Why don't you combine the solenoid with a floater? But anyway, for your purpose a simple basic timer would suffice, just check for greater or equal.

A small floater can of course be made with two contacts, a piece of cork, a piece of rope  and a paperclip or two (basic boyscout stuff)
BTW: A floater scales very well, from microscopic to industrial scale. Don't rely on timing alone.
« Last Edit: September 01, 2015, 10:28:30 am by Thaddy »
also related to equus asinus.