Skip to content

Instantly share code, notes, and snippets.

@ElectricRCAircraftGuy
Last active October 23, 2016 00:17
Show Gist options
  • Save ElectricRCAircraftGuy/38175a4a85c2ac97a934d85db28df395 to your computer and use it in GitHub Desktop.
Save ElectricRCAircraftGuy/38175a4a85c2ac97a934d85db28df395 to your computer and use it in GitHub Desktop.
UPDATE: LATEST VERSION FOUND HERE NOW (https://github.com/ElectricRCAircraftGuy/PyTiming/); A Windows and Linux-compatible Python module for ultra-great resolution timestamps (sub-microsecond), even in older versions of Python 3 which don't natively support (in the time module, for instance) high resolutions like this. Website: http://www.electr…
"""
timing.py
-create some low-level Arduino-like millis() (milliseconds) and micros()
(microseconds) timing functions for Python
By Gabriel Staples
http://www.ElectricRCAircraftGuy.com
-click "Contact me" at the top of my website to find my email address
Started: 11 July 2016
Updated: 7 Sept 2016
History (newest on top):
20160907 - v0.2.1 created - updated delay functions to use modulus operator to guarantee proper C uint32_t-like underflow subtraction behavior when the timer rolls over
20160813 - v0.2.0 created - added Linux capability
20160711 - v0.1.0 created - functions for Windows *only* (via the QPC timer)
References:
WINDOWS:
-personal (C++ code): GS_PCArduino.h
1) Acquiring high-resolution time stamps (Windows)
-https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
2) QueryPerformanceCounter function (Windows)
-https://msdn.microsoft.com/en-us/library/windows/desktop/ms644904(v=vs.85).aspx
3) QueryPerformanceFrequency function (Windows)
-https://msdn.microsoft.com/en-us/library/windows/desktop/ms644905(v=vs.85).aspx
4) LARGE_INTEGER union (Windows)
-https://msdn.microsoft.com/en-us/library/windows/desktop/aa383713(v=vs.85).aspx
-*****http://stackoverflow.com/questions/4430227/python-on-win32-how-to-get-
absolute-timing-cpu-cycle-count
LINUX:
-http://stackoverflow.com/questions/1205722/how-do-i-get-monotonic-time-durations-in-python
PYTHON (general):
-https://docs.python.org/3.5/library/ctypes.html - ctypes referene page
"""
import ctypes, os
#Constants:
VERSION = '0.2.1'
#-------------------------------------------------------------------
#MODULE FUNCTIONS:
#-------------------------------------------------------------------
#OS-specific low-level timing functions:
if (os.name=='nt'): #for Windows:
def micros():
"return a timestamp in microseconds (us)"
tics = ctypes.c_int64() #use *signed* 64-bit variables; see the "QuadPart" variable here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383713(v=vs.85).aspx
freq = ctypes.c_int64()
#get ticks on the internal ~2MHz QPC clock
ctypes.windll.Kernel32.QueryPerformanceCounter(ctypes.byref(tics))
#get the actual freq. of the internal ~2MHz QPC clock
ctypes.windll.Kernel32.QueryPerformanceFrequency(ctypes.byref(freq))
t_us = tics.value*1e6/freq.value
return t_us
def millis():
"return a timestamp in milliseconds (ms)"
tics = ctypes.c_int64() #use *signed* 64-bit variables; see the "QuadPart" variable here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383713(v=vs.85).aspx
freq = ctypes.c_int64()
#get ticks on the internal ~2MHz QPC clock
ctypes.windll.Kernel32.QueryPerformanceCounter(ctypes.byref(tics))
#get the actual freq. of the internal ~2MHz QPC clock
ctypes.windll.Kernel32.QueryPerformanceFrequency(ctypes.byref(freq))
t_ms = tics.value*1e3/freq.value
return t_ms
elif (os.name=='posix'): #for Linux:
#Constants:
CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h> here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h
#prepare ctype timespec structure of {long, long}
#-NB: use c_long (generally signed 32-bit) variables within the timespec C struct, per the definition here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h
class timespec(ctypes.Structure):
_fields_ =\
[
('tv_sec', ctypes.c_long),
('tv_nsec', ctypes.c_long)
]
#Configure Python access to the clock_gettime C library, via ctypes:
#Documentation:
#-ctypes.CDLL: https://docs.python.org/3.2/library/ctypes.html
#-librt.so.1 with clock_gettime: https://docs.oracle.com/cd/E36784_01/html/E36873/librt-3lib.html #-
#-Linux clock_gettime(): http://linux.die.net/man/3/clock_gettime
librt = ctypes.CDLL('librt.so.1', use_errno=True)
clock_gettime = librt.clock_gettime
#specify input arguments and types to the C clock_gettime() function
# (int clock_ID, timespec* t)
clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
def monotonic_time():
"return a timestamp in seconds (sec)"
t = timespec()
#(Note that clock_gettime() returns 0 for success, or -1 for failure, in
# which case errno is set appropriately)
#-see here: http://linux.die.net/man/3/clock_gettime
if clock_gettime(CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0:
#if clock_gettime() returns an error
errno_ = ctypes.get_errno()
raise OSError(errno_, os.strerror(errno_))
return t.tv_sec + t.tv_nsec*1e-9 #sec
def micros():
"return a timestamp in microseconds (us)"
return monotonic_time()*1e6 #us
def millis():
"return a timestamp in milliseconds (ms)"
return monotonic_time()*1e3 #ms
#Private module function
#-see here for use of underscore to make "module private": http://stackoverflow.com/questions/1547145/defining-private-module-functions-in-python/1547160#1547160
#-see here for example of constrain function: http://stackoverflow.com/questions/34837677/a-pythonic-way-to-write-a-constrain-function/34837691
def _constrain(val, min_val, max_val):
"constrain a number to be >= min_val and <= max_val"
if (val < min_val):
val = min_val
elif (val > max_val):
val = max_val
return val
#Other timing functions:
def delay(delay_ms):
"delay for delay_ms milliseconds (ms)"
#constrain the commanded delay time to be within valid C type uint32_t limits
delay_ms = _constrain(delay_ms, 0, (1<<32)-1)
t_start = millis()
while ((millis() - t_start)%(1<<32) < delay_ms): #use modulus to force C uint32_t-like underflow behavior
pass #do nothing
return
def delayMicroseconds(delay_us):
"delay for delay_us microseconds (us)"
#constrain the commanded delay time to be within valid C type uint32_t limits
delay_us = _constrain(delay_us, 0, (1<<32)-1)
t_start = micros()
while ((micros() - t_start)%(1<<32) < delay_us): #use modulus to force C uint32_t-like underflow behavior
pass #do nothing
return
#-------------------------------------------------------------------
#EXAMPLES:
#-------------------------------------------------------------------
#Only executute this block of code if running this module directly,
#*not* if importing it
#-see here: http://effbot.org/pyfaq/tutor-what-is-if-name-main-for.htm
if __name__ == "__main__": #if running this module as a stand-alone program
#print loop execution time 100 times, using micros()
tStart = micros() #us
for x in range(0, 100):
tNow = micros() #us
dt = tNow - tStart #us; delta time
tStart = tNow #us; update
print("dt(us) = " + str(dt))
#print loop execution time 100 times, using millis()
print("\n")
tStart = millis() #ms
for x in range(0, 100):
tNow = millis() #ms
dt = tNow - tStart #ms; delta time
tStart = tNow #ms; update
print("dt(ms) = " + str(dt))
#print a counter once per second, for 5 seconds, using delay
print("\nstart")
for i in range(1,6):
delay(1000)
print(i)
#print a counter once per second, for 5 seconds, using delayMicroseconds
print("\nstart")
for i in range(1,6):
delayMicroseconds(1000000)
print(i)
"""
sleep_test.py
-see what the resolution of the time.sleep() function is
By Gabriel Staples
http://www.ElectricRCAircraftGuy.com
-click "Contact me" at the top of my website to find my email address
13 Aug. 2016
"""
import time
import GS_timing as timing
def delayMicroseconds(n):
time.sleep(n / 1000000.)
def delayMillisecond(n):
time.sleep(n / 1000.)
t_start = 0
t_end = 0
#using time.sleep
print('using time.sleep')
print('delayMicroseconds(1)')
for x in range(10):
t_start = timing.micros() #us
delayMicroseconds(1)
t_end = timing.micros() #us
print('dt (us) = ' + str(t_end - t_start))
print('delayMicroseconds(2000)')
for x in range(10):
t_start = timing.micros() #us
delayMicroseconds(2000)
t_end = timing.micros() #us
print('dt (us) = ' + str(t_end - t_start))
#using GS_timing
print('\nusing GS_timing')
print('timing.delayMicroseconds(1)')
for x in range(10):
t_start = timing.micros() #us
timing.delayMicroseconds(1)
t_end = timing.micros() #us
print('dt (us) = ' + str(t_end - t_start))
print('timing.delayMicroseconds(2000)')
for x in range(10):
t_start = timing.micros() #us
timing.delayMicroseconds(2000)
t_end = timing.micros() #us
print('dt (us) = ' + str(t_end - t_start))
"""
SAMPLE RESULTS ON MY WINDOWS 8.1 MACHINE:
using time.sleep
delayMicroseconds(1)
dt (us) = 2872.059814453125
dt (us) = 886.3939208984375
dt (us) = 770.4649658203125
dt (us) = 1138.7698974609375
dt (us) = 1426.027099609375
dt (us) = 734.557861328125
dt (us) = 10617.233642578125
dt (us) = 9594.90576171875
dt (us) = 9155.299560546875
dt (us) = 9520.526611328125
delayMicroseconds(2000)
dt (us) = 8799.3056640625
dt (us) = 9609.2685546875
dt (us) = 9679.5439453125
dt (us) = 9248.145263671875
dt (us) = 9389.721923828125
dt (us) = 9637.994262695312
dt (us) = 9616.450073242188
dt (us) = 9592.853881835938
dt (us) = 9465.639892578125
dt (us) = 7650.276611328125
using GS_timing
timing.delayMicroseconds(1)
dt (us) = 53.3477783203125
dt (us) = 36.93310546875
dt (us) = 36.9329833984375
dt (us) = 34.8812255859375
dt (us) = 35.3941650390625
dt (us) = 40.010986328125
dt (us) = 38.4720458984375
dt (us) = 56.425537109375
dt (us) = 35.9072265625
dt (us) = 36.420166015625
timing.delayMicroseconds(2000)
dt (us) = 2039.526611328125
dt (us) = 2046.195068359375
dt (us) = 2033.8841552734375
dt (us) = 2037.4747314453125
dt (us) = 2032.34521484375
dt (us) = 2086.2059326171875
dt (us) = 2035.4229736328125
dt (us) = 2051.32470703125
dt (us) = 2040.03955078125
dt (us) = 2027.215576171875
SAMPLE RESULTS ON MY RASPBERRY PI VERSION 1 B+:
using time.sleep
delayMicroseconds(1)
dt (us) = 1022.0
dt (us) = 417.0
dt (us) = 407.0
dt (us) = 450.0
dt (us) = 2078.0
dt (us) = 393.0
dt (us) = 1297.0
dt (us) = 878.0
dt (us) = 1135.0
dt (us) = 2896.0
delayMicroseconds(2000)
dt (us) = 2746.0
dt (us) = 2568.0
dt (us) = 2512.0
dt (us) = 2423.0
dt (us) = 2454.0
dt (us) = 2608.0
dt (us) = 2518.0
dt (us) = 2569.0
dt (us) = 2548.0
dt (us) = 2496.0
using GS_timing
timing.delayMicroseconds(1)
dt (us) = 572.0
dt (us) = 673.0
dt (us) = 1084.0
dt (us) = 561.0
dt (us) = 728.0
dt (us) = 576.0
dt (us) = 556.0
dt (us) = 584.0
dt (us) = 576.0
dt (us) = 578.0
timing.delayMicroseconds(2000)
dt (us) = 2741.0
dt (us) = 2466.0
dt (us) = 2522.0
dt (us) = 2810.0
dt (us) = 2589.0
dt (us) = 2681.0
dt (us) = 2546.0
dt (us) = 3090.0
dt (us) = 2600.0
dt (us) = 2400.0
"""
"""
test.py
By Gabriel Staples
http://www.ElectricRCAircraftGuy.com
-click "Contact me" at the top of my website to find my email address
13 Aug. 2016
"""
import GS_timing as timing
print(timing.micros())
print(timing.millis())
print('version = ' + timing.VERSION)
print(timing._constrain(10,11,15)) #11
"""
test.py
By Gabriel Staples
http://www.ElectricRCAircraftGuy.com
-click "Contact me" at the top of my website to find my email address
13 Aug. 2016
"""
from GS_timing import *
print(micros())
print(millis())
print('version = ' + VERSION)
#SHOULD PRODUCE AN ERROR, since _constrain is module private
#-see here: http://stackoverflow.com/questions/1547145/defining-private-module-functions-in-python/1547160#1547160
print(_constrain(10,11,15)) #YEP! PRODUCES ERROR!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment