Timing things accurately
thomas_braun
I'm currently using background functions for waiting a specific time during two data acquisition cycles. I'm using a background function as I want Igor to be responsive and not locked up.
Now I'm faced with the task of getting this waiting to be more accurate. From reading the documentation it does not seem to be possible to get the background functions execute with a finer granularity as 1 tick.
I would have thought that using Sleep could help here, e.g. I could do that in the background function if only a couple of ticks are left. But that seems really not up to the precision I need according to my investigation.
So should I do busy waiting with stopmstimer(-2)?
The first graph show sthe granularity of each timer. The second the sleep accuracy.
Function CompareTiming()
variable i, numRuns = 1000
variable reltimeDateTime, reltimemstimer, reltimeticks
make/O/D/N=(numruns, 3) data
reltimeDateTime = DateTime
reltimeTicks = ticks
reltimeMstimer = stopmstimer(-2)
for(i = 0; i < numRuns; i += 1)
data[i][0] = datetime
data[i][1] = ticks
data[i][2] = stopmstimer(-2)
// printf "i: %d, DateTime: %g, ticks: %g, mstimer: %g\r", i, data[i][0], data[i][1], data[i][2]
endfor
data[][0] = data[p][0]- reltimedatetime
data[][1] = data[p][1]/60 - reltimeticks/60
data[][2] = data[p][2]/1e6 - reltimemstimer/1e6
End
Function CheckSleepTiming()
variable i, numruns = 100, reftime, interval = 0.1
Make/O/n=(numruns)/d dataSleep
reftime = stopmstimer(-2)
for(i = 0; i < numRuns; i += 1)
sleep/s interval
datasleep[i] = stopmstimer(-2)
endfor
datasleep[] = (datasleep[p] - reftime)/ 1e6 - p * interval
End
Window Graph1() : Graph
PauseUpdate; Silent 1 // building window...
Display /W=(127.5,305.75,824.25,789.5) data[*][0],data[*][1],data[*][2]
ModifyGraph mode=4
ModifyGraph rgb(data)=(0,0,0),rgb(data#1)=(65535,16385,16385),rgb(data#2)=(2,39321,1)
Legend/C/N=text0/J/A=MC/X=-17.73/Y=18.47 "\\s(data) DateTime\r\\s(data#1) ticks\r\\s(data#2) stopmstimer(-2)"
EndMacro
Window Graph3() : Graph
PauseUpdate; Silent 1 // building window...
Display /W=(138.75,822.5,727.5,1221.5) dataSleep
ModifyGraph mode=4
Legend/C/N=text0/J/X=53.03/Y=5.11 "\\s(dataSleep) dataSleep"
EndMacro
variable i, numRuns = 1000
variable reltimeDateTime, reltimemstimer, reltimeticks
make/O/D/N=(numruns, 3) data
reltimeDateTime = DateTime
reltimeTicks = ticks
reltimeMstimer = stopmstimer(-2)
for(i = 0; i < numRuns; i += 1)
data[i][0] = datetime
data[i][1] = ticks
data[i][2] = stopmstimer(-2)
// printf "i: %d, DateTime: %g, ticks: %g, mstimer: %g\r", i, data[i][0], data[i][1], data[i][2]
endfor
data[][0] = data[p][0]- reltimedatetime
data[][1] = data[p][1]/60 - reltimeticks/60
data[][2] = data[p][2]/1e6 - reltimemstimer/1e6
End
Function CheckSleepTiming()
variable i, numruns = 100, reftime, interval = 0.1
Make/O/n=(numruns)/d dataSleep
reftime = stopmstimer(-2)
for(i = 0; i < numRuns; i += 1)
sleep/s interval
datasleep[i] = stopmstimer(-2)
endfor
datasleep[] = (datasleep[p] - reftime)/ 1e6 - p * interval
End
Window Graph1() : Graph
PauseUpdate; Silent 1 // building window...
Display /W=(127.5,305.75,824.25,789.5) data[*][0],data[*][1],data[*][2]
ModifyGraph mode=4
ModifyGraph rgb(data)=(0,0,0),rgb(data#1)=(65535,16385,16385),rgb(data#2)=(2,39321,1)
Legend/C/N=text0/J/A=MC/X=-17.73/Y=18.47 "\\s(data) DateTime\r\\s(data#1) ticks\r\\s(data#2) stopmstimer(-2)"
EndMacro
Window Graph3() : Graph
PauseUpdate; Silent 1 // building window...
Display /W=(138.75,822.5,727.5,1221.5) dataSleep
ModifyGraph mode=4
Legend/C/N=text0/J/X=53.03/Y=5.11 "\\s(dataSleep) dataSleep"
EndMacro
October 19, 2017 at 02:09 pm - Permalink
As for background tasks, Igor's outer loop fires approximately every 20 ms (50 Hz). So setting the period of a background task to 1 tick won't result in consistent inter-execution intervals.
If you need very precise timing, you shouldn't use background tasks (or sleep). Instead, sit in a tight loop that uses StopMSTimer(-2) to determine when to break out of the loop. And if you do this, your tight loop should be a do...while loop, not a for...endfor loop, because the later sometimes allows events to be processed to keep Igor responsive (and if you need tight timing, you don't want events to be processed).
October 19, 2017 at 04:00 pm - Permalink
October 19, 2017 at 09:46 pm - Permalink
I will look into making StopMSTimer (and also StartMSTimer) fully threadsafe for Igor 8.
October 20, 2017 at 08:49 am - Permalink
Even if you get Igor to time something with sufficient accuracy, I think you will find that software timing of any kind is prone to erratic behavior. Sometimes the erratic behavior will only affect you once in a few hours, so that during development you think you have the solution, only to find over a period of months (or years!) that the solution is almost but not quite good enough.
John Weeks
WaveMetrics, Inc.
support@wavemetrics.com
October 20, 2017 at 09:30 am - Permalink
October 21, 2017 at 08:23 am - Permalink
If you dont want to block Igor's main running loop, you can put the timming function in a preemptive thread. However, the CPU load is still very high if you just using a loop to check the value StopMSTimer returned.
Also, Igor 6.37 seems to be more accurate compared to Igor 7 when N becomes bigger in the same condition...
At last, I quite agree that it is really difficult to get very accurate timming just using software.
variable t0=stopmstimer(-2)
do
if(stopmstimer(-2)-t0-10000>0) //interval = 0.01 s
return round(stopmstimer(-2)-t0)
break
endif
while(1)
end
function f2() //use Sleep
variable t0=stopmstimer(-2)
Sleep/S 0.01
return round(stopmstimer(-2)-t0)
end
function test(N)
variable N
make/N=(N)/O ddd1,ddd2
variable i
for(i=0;i<N;i+=1)
ddd1[i]=f1()
ddd2[i]=f2()
endfor
checkdisplayed ddd1
if(!V_Flag)
display ddd1
display ddd1,ddd2
ModifyGraph lsize=2,rgb(ddd2)=(0,0,65280)
Legend/C/N=text0/J/F=0/A=MC "\\s(ddd1) StopMSTimer\r\\s(ddd2) Sleep"
endif
end
//use multi-thread
threadsafe function f3()
variable t0=stopmstimer(-2)
do
if(stopmstimer(-2)-t0-10000>0) //interval = 0.01 s
return round(stopmstimer(-2)-t0)
break
endif
while(1)
end
function test2(N)
variable N
variable/G tgID,i,index
make/O/N=(N) ddd1
tgID=ThreadGroupCreate(1)
for(i=0;i<N;i+=1)
threadstart tgID,0,f3()
index=threadgroupwait(tgID,inf)
ddd1[i]=threadreturnvalue(tgID,index)
endfor
checkdisplayed ddd1
if(!V_Flag)
display ddd1
endif
end
October 22, 2017 at 04:24 pm - Permalink
We should probably add a note to the Sleep operation that makes it clear that the operation is not intended to be accurate for very small sleep durations.
October 22, 2017 at 02:45 pm - Permalink
However, when I say the timing accuracy behavior in different Igor version I mean StopMSTimer but not Sleep. follows may make what I want to say more clear:
Variable t0=StopMSTimer(-2),dt
do
dt=StopMSTimer(-2)-t0
if(dt>10000)
return dt
endif
while(1)
End
Function Test(N)
Variable N
Make/O/N=(N) ddd1
Variable i
for(i=0;i<N;i+=1)
ddd1[i]=f1()
endfor
CheckDisplayed ddd1
if(!V_Flag)
WaveStats/Q ddd1
Display ddd1
TextBox/N=tb1 "The Standard Deviation is " + num2str(V_sdev) + " us"
ModifyGraph highTrip(left)=100000
ModifyGraph mode=3,marker=8
else
WaveStats/Q ddd1
TextBox/C/N=tb1 "The Standard Deviation is " + num2str(V_sdev) + " us"
endif
End
Result in my case:
1 Igor 6.37,win
with N=1000, the standard deviation is typically less than 0.1 micro seconds
2 Igor 7.02, win
with N=1000, the standard deviation is typically about several tens of micro seconds (20~40)
FYI
October 22, 2017 at 07:11 pm - Permalink
In any case, if you need to do what this test does and you need high accuracy, you are using the wrong program on the wrong operating system.
October 23, 2017 at 06:39 am - Permalink
I do know that trying to time things accurately in the low ms range on a general purpose operating system is a difficult task. But I can't do the timing in hardware as we currently don't use NI hardware but ITC/Heka hardware. Additionally I can't do everything in hardware as I want to execute Igor code at some defined moments and I did not find a PLC supporting Igor ;).
@adam: I'll look into the do/while loop thing.
October 27, 2017 at 12:12 pm - Permalink
Does that mean it is more accurate on MacOSX?
October 27, 2017 at 12:12 pm - Permalink
Do you mean to say that ITC doesn't have a programmable pulse/trigger/clock generator? Or perhaps that need is delegated to the stimulator? Of course, for any of that to be useful, you need to have something that calls Igor, like NIDAQ Tools MX hooks (shameless plug :).
I think that means you need to find a program that runs on RTOS :)
John Weeks
WaveMetrics, Inc.
support@wavemetrics.com
October 27, 2017 at 12:41 pm - Permalink
Yes, exactly.
October 27, 2017 at 02:03 pm - Permalink
With the ITC hardware I have to manually monitor the fifo and stop when I've collected enough data. And of course we are already planning to support NI hardware.
Yeep that would be the best solution. But for that I would also like to have a "IgorProToC" XOP.
October 30, 2017 at 01:48 pm - Permalink