Skip to content

Commit d60ad5c

Browse files
committed
docs/utime: Describe new semantics of ticks_diff() (signed ring arithmetics).
1 parent 153665f commit d60ad5c

1 file changed

Lines changed: 62 additions & 12 deletions

File tree

docs/library/utime.rst

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,22 @@ Functions
8282

8383
.. function:: ticks_ms()
8484

85-
Returns an increasing millisecond counter with arbitrary reference point,
86-
that wraps after some (unspecified) value. The value should be treated as
87-
opaque, suitable for use only with ticks_diff().
85+
Returns an increasing millisecond counter with an arbitrary reference point,
86+
that wraps around after some value. This value is not explicitly exposed,
87+
but we will refer to it as `TICKS_MAX` to simplify discussion. Period of
88+
the values is `TICKS_PERIOD = TICKS_MAX + 1`. `TICKS_PERIOD` is guaranteed
89+
to be a power of two, but otherwise may differ from port to port. The same
90+
period value is used for all of ticks_ms(), ticks_us(), ticks_cpu() functions
91+
(for simplicity). Thus, these functions will return a value in range
92+
[0 .. `TICKS_MAX`], inclusive, total `TICKS_PERIOD` values. Not that only
93+
non-negative values are used. For the most part, you should treat values
94+
return by these functions as opaque. The only operations available for them
95+
are ``ticks_diff()`` and ``ticks_add()`` functions described below.
96+
97+
Note: Performing standard mathematical operations (+, -) on these value
98+
will lead to invalid result. Performing such operations and then passing
99+
results as arguments to ``ticks_diff()`` or ``ticks_add()`` will also lead to
100+
invalid result.
88101

89102
.. function:: ticks_us()
90103

@@ -105,22 +118,59 @@ Functions
105118

106119
.. only:: port_unix or port_pyboard or port_wipy or port_esp8266
107120

108-
.. function:: ticks_diff(old, new)
121+
.. function:: ticks_diff(ticks1, ticks2)
109122

110-
Measure period between consecutive calls to ticks_ms(), ticks_us(), or ticks_cpu().
111-
The value returned by these functions may wrap around at any time, so directly
112-
subtracting them is not supported. ticks_diff() should be used instead. "old" value should
113-
actually precede "new" value in time, or result is undefined. This function should not be
114-
used to measure arbitrarily long periods of time (because ticks_*() functions wrap around
115-
and usually would have short period). The expected usage pattern is implementing event
116-
polling with timeout::
123+
Measure ticks difference between values returned from ticks_ms(), ticks_us(), or ticks_cpu()
124+
functions. The argument order is the same as for subtraction operator,
125+
``tick_diff(ticks1, ticks2)`` has the same meaning as ``ticks1 - ticks2``. However, values returned by
126+
ticks_ms(), etc. functions may wrap around, so directly using subtraction on them will
127+
produce incorrect result. That is why ticks_diff() is needed, it implements modular
128+
(or more specifically, ring) arithmetics to produce correct result even for wrap-around
129+
values (as long as they not too distant inbetween, see below). The function returns
130+
**signed** value in the range [`-TICKS_PERIOD/2` .. `TICKS_PERIOD/2-1`] (that's a typical
131+
range definition for two's-complement signed binary integers). If the result is negative,
132+
it means that `ticks1` occured earlier in time than `ticks2`. Otherwise, it means that
133+
`ticks1` was after `ticks2`. This holds `only` if `ticks1` and `ticks2` are apart from
134+
each other for no more than `TICKS_PERIOD/2-1` ticks. If that does not hold, incorrect
135+
result will be returned. Specifically, if 2 tick values are apart for `TICKS_PERIOD/2-1`
136+
ticks, that value will be returned by the function. However, if `TICKS_PERIOD/2` of
137+
real-time ticks has passed between them, the function will return `-TICKS_PERIOD/2`
138+
instead, i.e. result value will wrap around to the negative range of possible values.
139+
140+
141+
``ticks_diff()`` is designed to accommodate various usage patterns, among them:
142+
143+
Polling with timeout. In this case, the order of events is known, and you will deal
144+
only with positive results of ``ticks_diff()``::
117145

118146
# Wait for GPIO pin to be asserted, but at most 500us
119147
start = time.ticks_us()
120148
while pin.value() == 0:
121-
if time.ticks_diff(start, time.ticks_us()) > 500:
149+
if time.ticks_diff(time.ticks_us(), start) > 500:
122150
raise TimeoutError
123151

152+
Scheduling events. In this case, ``ticks_diff()`` result may be negative
153+
if an event is overdue::
154+
155+
# This code snippet is not optimized
156+
now = time.ticks_ms()
157+
scheduled_time = task.scheduled_time()
158+
if ticks_diff(now, scheduled_time) > 0:
159+
print("Too early, let's nap")
160+
sleep_ms(ticks_diff(now, scheduled_time))
161+
task.run()
162+
elif ticks_diff(now, scheduled_time) == 0:
163+
print("Right at time!")
164+
task.run()
165+
elif ticks_diff(now, scheduled_time) < 0:
166+
print("Oops, running late, tell task to run faster!")
167+
task.run(run_faster=true)
168+
169+
Note: Do not pass ``time()`` values to ``ticks_diff()``, and should use
170+
normal mathematical operations on them. But note that ``time()`` may (and will)
171+
also overflow. This is known as https://en.wikipedia.org/wiki/Year_2038_problem .
172+
173+
124174
.. function:: time()
125175

126176
Returns the number of seconds, as an integer, since the Epoch, assuming that underlying

0 commit comments

Comments
 (0)