Recent

Author Topic: How to use timers for interrupts?  (Read 10426 times)

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: How to use timers for interrupts?
« Reply #30 on: July 14, 2022, 11:54:03 pm »
Quote
are you sure that the right compiler is used? If you copy all messages that Lazarus prints (use the right click menu on the message window so that you can copy all including the hidden ones), what does the version information of FPC say?

My bad on this, the compiler path was wrong. I managed to run it. Even without sleep, it performs like sleep in performance, and the cpu stays @ 100%.

Let me try to clear things out, as y.ivanov correctly said its quite vague.
I was under the impression that I could do something similar to sleep/delay using cpu's registers or bios (I did some search, like here: http://www.delphigroups.info/2/c0/14324.html), or using those "interrupt handlers" (no experience on these), and of cource to not use a repeating loop to poll a timer and thus consume cpu cycles. Did I make things worse?
You haven't made it worse, doing your own research is always praiseworthy.
The DOS times are long gone, now you can't use such things as hardware interrupts unless you're driver developer.
At the other hand the software environment is vastly improved now and you have plenty of options at your disposal.
The only thing is that you must have a knowledge how to use them.
I suspect the sleep/delay you want to do is part of some bigger plan you have, but what's the point, what you don't like about the existing ones?
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

prodingus

  • Jr. Member
  • **
  • Posts: 83
Re: How to use timers for interrupts?
« Reply #31 on: July 15, 2022, 12:44:13 am »
Quote
You haven't made it worse, doing your own research is always praiseworthy.
The DOS times are long gone, now you can't use such things as hardware interrupts unless you're driver developer.
At the other hand the software environment is vastly improved now and you have plenty of options at your disposal.
The only thing is that you must have a knowledge how to use them.
I suspect the sleep/delay you want to do is part of some bigger plan you have, but what's the point, what you don't like about the existing ones?

About DOS times, it's as much as you said, but after the reading I 've done, damn those days were good!
As I posted here and in the lnet thread, I need more precise control for limiting CPU usage. Sleep/delay (and some other methods, waitable-something (the link is in my laptop, away)) reduce the CPU usage but their, lets say it quantum, is very large (what about microsecond or nanosecond delay?). Still, having the CPU *magicaly* triggering the next code line instead of running a loop and asking it continiously, I cannot find any reason why not.
lazarus 2.2.0, FPC 3.2.2

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: How to use timers for interrupts?
« Reply #32 on: July 15, 2022, 02:32:47 am »
Quote
You haven't made it worse, doing your own research is always praiseworthy.
The DOS times are long gone, now you can't use such things as hardware interrupts unless you're driver developer.
At the other hand the software environment is vastly improved now and you have plenty of options at your disposal.
The only thing is that you must have a knowledge how to use them.
I suspect the sleep/delay you want to do is part of some bigger plan you have, but what's the point, what you don't like about the existing ones?

About DOS times, it's as much as you said, but after the reading I 've done, damn those days were good!
As I posted here and in the lnet thread, I need more precise control for limiting CPU usage. Sleep/delay (and some other methods, waitable-something (the link is in my laptop, away)) reduce the CPU usage but their, lets say it quantum, is very large (what about microsecond or nanosecond delay?). Still, having the CPU *magicaly* triggering the next code line instead of running a loop and asking it continiously, I cannot find any reason why not.
(micro/nano seconds are too small quantities, you must have dedicated hardware to perform tasks at such a speed, as it is in micro-controllers)
But you should look more carefully at my example in reply #18.
At line 46:
Code: [Select]
Sleep(Min(100, TckTimerRemaining(T))); Here the program will sleep for the remaining time but no more than 100 ms. This is with a dual purpose: first - not to loop continuously, and second - for giving a chance to do some other work at regular intervals (100 ms) if needed.

At the first iteration it will {do some work, line 37}, will sleep 100 ms at line 46 then will wake up and repeat. Next iterations up to 20-th will do the same until the condition at line 39 satisfied (timer expired) then it will write I, increment I and re-init the timer. Thus the tick counter will be asked just 20 times (2000/100), not continuously. The figures can be modified as needed, of course. But the ratio between the timer delay and the sleep parameter is what matters for how many times the timer will be asked.

If you remove the sleep at line 46 it will bump up to 100% CPU just because the thread is always active, but even then it will be preempted by the scheduler.

If you leave the sleep, even with a minimal value of e.g. 1, it will send the thread into the 'waiting' queue of the scheduler and that will dramatically reduce the %CPU almost to 0. You can try it.

(Next explanation is greatly simplified) OS threads have a 'state': one of 'active', 'ready', 'waiting', etc. Usually one of them is active, few are 'ready' and all others 'waiting'. Each time the quanta of the 'active' thread expires, the scheduler makes it 'ready', picks one of other 'ready' threads and makes it the current 'active'. Those 'waiting' doesn't take CPU time, they are just waiting for their event to happen (timer or I/O). After the event they became 'ready' and candidates to be next 'active'.

According to this:
  • if you don't use sleep, the thread will run until quanta expires and then will become 'ready'
  • if you use sleep(0) (aka yield), the thread will voluntarily give up the remaining of its quanta and will become 'ready'
  • if you use sleep(n) (n>0), the thread will yield and then will become 'waiting' for time of n ms and after that - 'ready'
In the first two cases the CPU will be at 100% and all such threads will use the CPU more or less proportionally
In the third case the thread will use just the time for executing {do some work} on each 100 ms which usually is a tiny fraction and trends to 0% compared to all others.

In addition to that there is always a special Idle process which never 'waits' anything but is of very low priority and the purpose is to always have at least one 'ready' thread. But it's not shown on the CPU graph. 

 Hope that shed some light on the matter
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

AlanTheBeast

  • Sr. Member
  • ****
  • Posts: 407
  • My software never cras....
Re: How to use timers for interrupts?
« Reply #33 on: July 15, 2022, 11:16:28 pm »
Hi!
Before the question, let me clear: I am using pascal without gui, objects, forms, classes, threads, so bear with me.

I need to set a timer, an interval, and when the time "passes" interrupt to execute some code. Maybe something like ttimer or TFPTimer, but the documentation is poor and object-oriented (I have no idea how to use them). Sleep/delay functions are completelly useless.


Simple (with thread, no object code). 
W/o resorting to object code, one can setup a thread, enter it, set a sleep, call the procedure of interest and loop forever.

Code: Pascal  [Select][+][-]
  1. Program Mover; uses cthreads, sysutils;
  2.  
  3. TYPE
  4.         CRec=record
  5.                                 delay: longword;
  6.                                 GotIT: boolean;
  7.                                 Target: pointer;
  8.                         end;
  9.  
  10. function Tada:longword;      ///.        or procedure Tada w/o the exit (0);
  11. BEGIN
  12.         writeln ('Ta-da!');
  13.         exit(0);
  14. END;
  15.  
  16. Function DelThread (z:pointer):ptrint;  
  17. VAR
  18.         Sp: longword;
  19.         V:  longword;
  20.         fn: pointer;
  21. BEGIN
  22.         With CRec(Z^) do
  23.                 begin
  24.                         Sp := delay;     //grab the delay value
  25.                         fn := Target;    // set up the target
  26.                         GotIt := true;   //signal that it's caught
  27.                 end;
  28.  
  29.         Repeat
  30.                 Write (' What? ');
  31.                 Sleep(Sp);
  32.                 asm
  33.                         call fn              // should work on any *86, but I don't guarantee it!
  34.                 end;
  35.         Until false;
  36.         exit(0);
  37. End;
  38. VAR
  39.         DCon: CRec;
  40. BEGIN
  41.         DCon.Delay := 300;
  42.         DCon.GotIT := false;
  43.         DCon.Target := @Tada;
  44.         BeginThread (@DelThread, @DCon);
  45.  
  46.         Repeat sleep(1)until DCOn.GotIT;
  47.  
  48.         Repeat Sleep (10000)
  49.    until false;
  50. END.
  51.  

You could also use the @DCon to pass changed delay values and/or different target functions as you go.
Just put the With CRec(Z^) into the loop...
« Last Edit: July 16, 2022, 01:44:26 am by AlanTheBeast »
Everyone talks about the weather but nobody does anything about it.
..Samuel Clemens.

prodingus

  • Jr. Member
  • **
  • Posts: 83
Re: How to use timers for interrupts?
« Reply #34 on: July 19, 2022, 10:20:58 pm »
Code: Pascal  [Select][+][-]
  1. program delay_new2;
  2.  
  3. uses
  4.         Classes, StreamIO, Windows;
  5. var
  6.         i, j, k: int64;
  7.     now, start, start2, d1, d2, delta: int64;
  8.     itter: array [1..10,1..3] of int64;
  9.     avg: array [1..3] of int64;
  10.  
  11.     h: Cardinal;
  12.     threadtstart,threadtnow: int64;
  13.     pid: DWORD;
  14.  
  15. type
  16.         TConsoleOutputStream = class(TStream)
  17.     function Write(const Buffer; Count: longint): longint; override;
  18. end;
  19.  
  20. function TConsoleOutputStream.Write(const Buffer; Count: longint): longint;
  21. var
  22.     charcount: longword=0;
  23. begin
  24.     WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), @Buffer, Count, charcount, nil);
  25.     Result := charcount;
  26. end;
  27.  
  28. function getthreadtime(header:pointer):int64;
  29. var
  30.         mCreationTime,mExitTime,mKernelTime, mUserTime:_FILETIME;
  31. begin
  32.         GetProcessTimes(cardinal(header^),mCreationTime,mExitTime,mKernelTime,mUserTime);
  33.     result:=mKernelTime.dwLowDateTime + mUserTime.dwLowDateTime;
  34. end;
  35.  
  36. begin
  37.         AssignStream(Output, TConsoleOutputStream.Create);
  38.         Rewrite(Output);
  39.  
  40.     pid := GetCurrentProcessId;
  41.     h:=OpenProcess(PROCESS_QUERY_INFORMATION,false,PID);
  42.  
  43.  
  44.         i:=0;
  45.     j:=0;
  46.     start:=gettickcount64;
  47.     start2:=start;
  48.  
  49.  
  50.     threadtstart:=getthreadtime(@h);
  51.         repeat
  52.  
  53.         now:=gettickcount64;
  54.         d1:=start+100;
  55.         d2:=start2+1000;
  56.         inc(i);
  57.  
  58.         if now > d1 then begin        //from here
  59.                 delta:=now-start;
  60.                 start:=gettickcount64;
  61.  
  62.             sleep(20);
  63.  
  64.         end else                      //to here, central part
  65.                 if now > d2 then begin
  66.  
  67.                     threadtnow:=getthreadtime(@h);
  68.  
  69.  
  70.                 inc(j);
  71.                 itter[j,1]:=i;
  72.                 itter[j,2]:=delta;
  73.                 itter[j,3]:=round((threadtnow-threadtstart)/(gettickcount64-start2)/100);
  74.                 if j<10 then writeln('now: ',itter[j,1],' ',itter[j,2],' ',itter[j,3]) else begin
  75.                     for k:=1 to 10 do begin
  76.                         avg[1]:=avg[1]+itter[k,1];
  77.                         avg[2]:=avg[2]+itter[k,2];
  78.                         avg[3]:=avg[3]+itter[k,3];
  79.                     end;
  80.                     for k:=1 to 3 do avg[k]:=round(avg[k]/10);
  81.                     writeln('avg: ',avg[1],' ',avg[2],' ',avg[3],' -');
  82.                     for k:=1 to 3 do avg[k]:=0;
  83.                         j:=0;
  84.                 end;
  85.  
  86.                     start2:=gettickcount64;
  87.                         i:=0;
  88.  
  89.                         threadtstart:=getthreadtime(@h);
  90.                 end;
  91.  
  92.     until false;
  93. end.
  94.  
Is it me or the sleep is wacky at best? This is a small polling delayer (at least it limits the cpu usage, but doing so it undercuts perfomance). Feel free to try it with combinations of:

d1:=start+x where x 10, 25, 50, 100 and
sleep 1,2,5,10,20

Also, bind the process in one core/thread (I use THG task assigment manager).

@AlanTheBeast, I need a lot time to decompose and understand your "nested" but elegant code.
« Last Edit: July 19, 2022, 10:35:29 pm by prodingus »
lazarus 2.2.0, FPC 3.2.2

AlanTheBeast

  • Sr. Member
  • ****
  • Posts: 407
  • My software never cras....
Re: How to use timers for interrupts?
« Reply #35 on: July 20, 2022, 01:33:09 am »
@AlanTheBeast, I need a lot time to decompose and understand your "nested" but elegant code.

Nothing much there.  (Bad editing may make it look more nested than it is! ...)

0. Crec is used to pass info to/from DelThread - including the address of Tada.

1. Tada is a procedure or function to be called at some beat.

2. DelThread is the continuously running thread that sleeps a lot.  It is what calls Tada based on the delay.

3. The main part: sets up the control record (DCon) with
      a delay period
      a signal back that the data has been received (gotit)
      the address of the procedure to be run (Tada).

Because DCon is in globals, the Gotit is not really needed, but if DCon were in a procedure or function and that pro/fun ended before
the thread DelThread could start, then there could be problems. ("out of scope" issue).

The call to Tada could also contain a sub package to be passed on to Tada and so on.

And of course, properly coded, you could use DCon to "throttle" the delay as needed as long as the sleep value is reread from DCon (in DelThread) and something else changes the value to DCOn by measuring v. GetTickCount64;

(If you're seeing some feedback notions there to 'tune' the sleep period, then that is the intent, but the 1ms sleep and indeterministic delay that actually occurs is not going to make that very fine.  On a Rasp Pi I can measure to the microsecond (actually to 1/54th of a µsec) ... though Sleep is still in 1ms bumps, alas).

PS: I've tested it extensively and can state categorically that Sleep (-7) does not transport anything back in time ....
« Last Edit: July 20, 2022, 10:35:56 pm by AlanTheBeast »
Everyone talks about the weather but nobody does anything about it.
..Samuel Clemens.

prodingus

  • Jr. Member
  • **
  • Posts: 83
Re: How to use timers for interrupts?
« Reply #36 on: July 25, 2022, 08:47:03 pm »
Sorry for the delayed answer. I posted this because I wanted to have confirmation by others besides me: I can get, lets say, 50.000.000 increments per second (lets call it performance) @ 80% cpu usage, and the same performance @ 90% cpu usage by changing delay and d1:=start+x
lazarus 2.2.0, FPC 3.2.2

Thaddy

  • Hero Member
  • *****
  • Posts: 19606
  • Glad to be alive.
Re: How to use timers for interrupts?
« Reply #37 on: July 25, 2022, 09:13:51 pm »
Code: Pascal  [Select][+][-]
  1. Repeat sleep(1)until DCOn.GotIT;
is an error. It should be simply
Code: Pascal  [Select][+][-]
  1. sleep(0);
Sleep(0) has a different meaning on Windows: it means that you voluntary give up time to other threads when needed.
If not needed, your thread keeps running without intervention.
Sleep() in any form is not suitable at all for exact timing purposes because of the above.
And sleep(1) or higher than 1 slows down your code - a lot -. Sleep(1) is a mistake.

In effect even sleep(0) is actually deprecated for your purpose, but there are alternatives. See msdn:
Read all of it carefully, since you are on Windows anyway
https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep

On a side note, it is a bit depressing to answer the same question over and over again. MSDN docs are free.
This simply is a case of RTFM.
« Last Edit: July 25, 2022, 09:34:24 pm by Thaddy »
Any "programmer" that knows only one programming language is not a programmer

440bx

  • Hero Member
  • *****
  • Posts: 6565
Re: How to use timers for interrupts?
« Reply #38 on: July 25, 2022, 10:56:50 pm »
Sleep(0) has a different meaning on Windows: it means that you voluntary give up time to other threads when needed.
that's what it's supposed to mean.  The reality is quite different.

Sleep() in any form is not suitable at all for exact timing purposes because of the above.
That shouldn't be a surprise, Sleep() is not meant to time anything other than the amount of time the scheduler won't give the thread that called it any cpu cycles.

And sleep(1) or higher than 1 slows down your code - a lot -. Sleep(1) is a mistake.
that is simply _wrong_.  If anything, it would be much more accurate to consider Sleep(0) a mistake.  That said, on some versions of Windows, it is "advisable" to manually set the timer resolution to a higher resolution for the time to be reasonably accurate.

On a side note, it is a bit depressing to answer the same question over and over again.
This simply is a case of RTFM.
It seems like you know how to read.  As far as testing how things _actually_ work, you have a lot of room for improvement. On your point of what's depressing, what could be eventually depressing is having people like you make authoritative statements about things they obviously know less than nothing about.

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 19606
  • Glad to be alive.
Re: How to use timers for interrupts?
« Reply #39 on: July 25, 2022, 11:28:40 pm »
Well, my answer is based on solid autoritative documentation, namely the official docs from Microsoft.
So go figure what I think about your post.... >:D
The kindest way to express it is ill-informed.
Opinions are not facts. I tend to rely on MSDN as fact. Do not spread fake news, you tend to do that rather often (at least more so than I do, mea culpa).
Again: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep
You can write, so it derives by sheer logic that you can read too....

For other readers: stick to the official documentation. If needed use the alternatives mentioned there.
« Last Edit: July 25, 2022, 11:45:40 pm by Thaddy »
Any "programmer" that knows only one programming language is not a programmer

440bx

  • Hero Member
  • *****
  • Posts: 6565
Re: How to use timers for interrupts?
« Reply #40 on: July 26, 2022, 12:44:45 am »
You're always very entertaining. :)

solid autoritative documentation, namely the official docs from Microsoft.
That sounds impressive but, let's see how "solid" and "authoritative" MS's documentation is.  Check out this page (from MS of course) https://docs.microsoft.com/en-us/windows/win32/api/windowsx/nf-windowsx-button_getcheck
where MS claims the macro has no return value BUT, the BM_GETCHECK message it sends returns the state of the button.  That's very nice, you execute a macro to _get_ something and the macro, supposedly, returns nothing.  That's really "solid" and "authoritative".

So go figure what I think about your post.... >:D
if somehow I managed to make you think, that is a rare accomplishment.

The kindest way to express it is ill-informed.
Indeed, you are ill-informed.  I suggest you don't believe everything you read.  Some "authoritative" people at one time claimed earth was at the center of the universe.  That said, I have to admit they were very authoritative, many of those who didn't agree were burned alive, e.g, Giordano Bruno.

Opinions are not facts.
You got that right.  Your opinion has no resemblance to fact.

I tend to rely on MSDN as fact.
I've noticed you tend to rely on MSDN.  I suggest you start relying on measurements instead.

Do not spread fake news, you tend to do that rather often (at least more so than I do, mea culpa).
you pulled your trump card.  When someone states a fact, you pull out the "fake news" card.  Did you get a hair transplant too ?

For other readers: stick to the official documentation. If needed use the alternatives mentioned there.
I encourage any reader of these posts to meticulously carry out their own measurements and reach a logical conclusion based on them.
« Last Edit: July 26, 2022, 12:51:42 am by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

kupferstecher

  • Hero Member
  • *****
  • Posts: 627
Re: How to use timers for interrupts?
« Reply #41 on: July 26, 2022, 01:27:29 pm »
Is it me or the sleep is wacky at best? This is a small polling delayer (at least it limits the cpu usage, but doing so it undercuts perfomance). Feel free to try it with combinations of:
I didn't dig into that topic, just saw you use GetTickcount64, in the docs for windows it says:

"The resolution of the GetTickCount64 function is limited to the resolution of the system timer, which is typically in the range of 10 milliseconds to 16 milliseconds."
https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64

prodingus

  • Jr. Member
  • **
  • Posts: 83
Re: How to use timers for interrupts?
« Reply #42 on: July 26, 2022, 05:33:02 pm »
Quote
On a side note, it is a bit depressing to answer the same question over and over again. MSDN docs are free.
This simply is a case of RTFM.
Yes I am on windoze (pun intended), yes there are things in every programming languages that rely on windoze's internals, but you cannot say "RTFM" in this; If I was on a forum about MSDN development, then I agree totally with you. Even FPC documetnation is really really bad (and that's not just my words) especially compared to the borland pascal's help manual (yes, I said it, that bad-ass .chm manual was epic). Plus, MSDN is kind more for c/c++ than any other language. Fell free to correct me if I am wrong on any on these. :)
lazarus 2.2.0, FPC 3.2.2

Thaddy

  • Hero Member
  • *****
  • Posts: 19606
  • Glad to be alive.
Re: How to use timers for interrupts?
« Reply #43 on: July 26, 2022, 06:25:53 pm »
The fpc documentation in excellent, just like MSDN is excellent.
Any "programmer" that knows only one programming language is not a programmer

AlanTheBeast

  • Sr. Member
  • ****
  • Posts: 407
  • My software never cras....
Re: How to use timers for interrupts?
« Reply #44 on: July 27, 2022, 07:56:57 pm »
Code: Pascal  [Select][+][-]
  1. Repeat sleep(1)until DCOn.GotIT;
is an error. It should be simply
Code: Pascal  [Select][+][-]
  1. sleep(0);
Sleep(0) has a different meaning on Windows: it means that you voluntary give up time to other threads when needed.
If not needed, your thread keeps running without intervention.
Sleep() in any form is not suitable at all for exact timing purposes because of the above.
And sleep(1) or higher than 1 slows down your code - a lot -. Sleep(1) is a mistake.

In effect even sleep(0) is actually deprecated for your purpose, but there are alternatives. See msdn:
Read all of it carefully, since you are on Windows anyway
https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep


My example for the OP was just that: an example to get him going with fast, cheap, easy code to do something close to wanted as described in his original post (avoid classes and threads ... well classes at least).  Sleep(0) is better?  Great. Go fer it - but even then any waiting threads will go...

Further, GotIt was only to signal back to the caller (the one in Sleep loop) that it was safe for the caller to continue and release (de-scope) the passed data block (had it been, for example, on the stack).  The thread that was started is independent of that sleep call - once it set "GotIt" the thread would continue on.

Clearly Sleep is not a real-time function other than "not sooner than...".

Thirdly, for the OP, in the example I gave, set the target type as "procedure", then you can simple use the name of the target instead of the asm call fn end;
Everyone talks about the weather but nobody does anything about it.
..Samuel Clemens.

 

TinyPortal © 2005-2018