pythonmultithreadingtimerraspberry-piperiodic-task

Python, Raspberry pi, call a task every 10 milliseconds precisely


I'm currently trying to have a function called every 10ms to acquire data from a sensor.

Basically I was triggering the callback from a gpio interrupt but I changed my sensor and the one I'm currently using doesn't have a INT pin to drive the callback.

So my goal is to have the same behavior but with an internal interrupt generated by a timer.

I tried this from this topic

import threading

def work (): 
  threading.Timer(0.25, work).start ()
  print(time.time())
  print "stackoverflow"

work ()

But when I run it I can see that the timer is not really precise and it's deviating over time as you can see.

1494418413.1584847
stackoverflow
1494418413.1686869
stackoverflow
1494418413.1788757
stackoverflow
1494418413.1890721
stackoverflow
1494418413.1992736
stackoverflow
1494418413.2094712
stackoverflow
1494418413.2196639
stackoverflow
1494418413.2298684
stackoverflow
1494418413.2400634
stackoverflow
1494418413.2502584
stackoverflow
1494418413.2604961
stackoverflow
1494418413.270702
stackoverflow
1494418413.2808678
stackoverflow
1494418413.2910736
stackoverflow
1494418413.301277
stackoverflow

So the timer is deviating by 0.2 milliseconds every 10 milliseconds which is quite a big bias after few seconds.

I know that python is not really made for "real-time" but I think there should be a way to do it.

If someone already have to handle time constraints with python I would be glad to have some advices.

Thanks.


Solution

  • This code works on my laptop - logs the delta between target and actual time - main thing is to minimise what is done in the work() function because e.g. printing and scrolling screen can take a long time.

    Key thing is to start the next timer based on difference between the time when that call is made and the target.

    I slowed down the interval to 0.1s so it is easier to see the jitter which on my Win7 x64 can exceed 10ms which would cause problems with passing a negative value to thte Timer() call :-o

    This logs 100 samples, then prints them - if you redirect to a .csv file you can load into Excel to display graphs.

    from multiprocessing import Queue
    import threading
    import time
    
    # this accumulates record of the difference between the target and actual times
    actualdeltas = []
    
    INTERVAL = 0.1
    
    def work(queue, target):
        # first thing to do is record the jitter - the difference between target and actual time
        actualdeltas.append(time.clock()-target+INTERVAL)
    #    t0 = time.clock()
    #    print("Current time\t" + str(time.clock()))
    #    print("Target\t" + str(target))
    #    print("Delay\t" + str(target - time.clock()))
    #    print()
    #    t0 = time.clock()
        if len(actualdeltas) > 100:
            # print the accumulated deltas then exit
            for d in actualdeltas:
                print d
            return
        threading.Timer(target - time.clock(), work, [queue, target+INTERVAL]).start()
    
    myQueue = Queue()
    
    target = time.clock() + INTERVAL
    work(myQueue, target)
    

    Typical output (i.e. don't rely on millisecond timing on Windows in Python):

    0.00947008617187
    0.0029628920052
    0.0121824719378
    0.00582923077099
    0.00131316206917
    0.0105631524709
    0.00437298744466
    -0.000251418553351
    0.00897956530515
    0.0028528821332
    0.0118192949105
    0.00546301269675
    0.0145723546788
    0.00910063698529