MicroPython 'time' Library: Delays & Timing

Contents

Introduction to the time Library

This article investigates methods from the generic MicroPython time library to:

  1. Code a defined length (blocking) delay during program execution
  2. Precisely time a process.

The MicroPython time library is a subset of the Python library of the same name.

The Pyboard-specific MicroPython pyb library provides similar methods and are discussed in the article MicroPython 'pyb' Library: Delays & Timing.

Delays During Code Execution

There are many instances when program execution must be held for a short period of time. A classic example occurs when the microcontroller requests a reading from a sensor then must wait while the sensor takes the reading and processes it ready for the microcontroller to receive the value.

From the time library there are three delay methods:

  1. time.sleep(seconds) : Delay for the given number of seconds.
  2. time.sleep_ms(ms) : Delay for the given number of milliseconds.
  3. time.sleep_us(us) : Delay for the given number of microseconds.

Example 1:
# Simulates a sensor that measures widgets.

# Algorithm:
#   (1) The microcontroller requests a measurement.
#   (2) The microcontroller must wait 500 ms for the sensor
#       to take the measurement and do any conversions.
#   (3) The microcontroller asks for the reading from the
#       sensor.

import time
import random

class WIDGETS():
    def Measurement(self):
        pass
    
    def Reading(self):
        return random.uniform(3.0, 5.0)
    
widgets = WIDGETS()
widgets.Measurement() # Request measurement
time.sleep_ms(500) # delay for 500 ms
print(widgets.Reading(), 'Widgets') # Get reading and report it

Sample Output:
4.5200632 Widgets
          

This example uses the uniform() method from the random MicroPython standard library. In this case it generates a random float value between 3.0 and 5.0 inclusive.

Timing a Process

Introducing a delay into the execution of the MicroPython program is often very useful. The main problem is this delay is blocking i.e. execution of code (except for interrupt handling) comes to a standstill till the delay interval times out.

Sometimes it's necessary for a process to continue but not beyond a set time limit. Or it might be necessary to know how long a process takes to complete.

The time library has four methods that are useful for accurately timing a process:

  1. time.ticks_ms() : Returns an increasing millisecond counter with an arbitrary reference point, that wraps around after some value.
  2. time.ticks_us() : Returns an increasing microsecond counter with an arbitrary reference point, that wraps around after some value.
  3. time.ticks_add(start, delta) : Offset ticks value by a given number, which can be either positive or negative.
  4. time.ticks_diff(ticks1, ticks2) : Returns the number of ticks from ticks1 - ticks2.

The methods time.ticks_ms() and time.ticks_us() count up to a maximum value then wrap back to zero. Direct arithmetic shouldn't be applied to the values from these methods.

This is why the methods time.ticks_add() and time.ticks_diff() are provided. They take into account this wraparound behaviour.

Example 2  calculates the sequence usually referred to as 'the sum of the squares of the first n  natural numbers'.

  •   Sum = 12 + 22 + 32 + ... + n2  

Since this series is infinite in nature the calculation will only be permitted to run for 700 microseconds.


Example 2:
# Calculates the sum of the squares of the first 'n' integers.
# The calculation is allowed to proceed for 700 microseconds.

import time

sum = n = 0
# Get current time + 700 microseconds.
finish = time.ticks_add(time.ticks_us(), 700)

# Calculate series for about 700 microseconds.
while time.ticks_diff(finish, time.ticks_us()) > 0:
    n += 1
    sum += n*n
    
print('n:', n, '  Sum:', sum)

Output:
n: 31   Sum: 10416
          

Example 3  times a process that needs to be measured in microseconds. In this case the actual process of doing the timing must be measured and offset.


Example 3:
# Program that uses methods from the MicroPython 'time' library
# to time a coded 200 us delay.

import time

# Calculate offset.
# This is the time in microseconds that is taken to measure
# the 'measuring' process.
start = time.ticks_us()
finish = time.ticks_us()
offset = time.ticks_diff(finish, start)
print('Offset(us):', offset)

# Time a delay of 200 microseconds
start = time.ticks_us()
pyb.udelay(200)
finish = time.ticks_us()
interval = time.ticks_diff(finish, start)
interval -= offset
print('Coded delay (us):', 200)
print('Measured delay (us):', interval)

Sample Output:
Offset(us): 21
Coded delay (us): 200
Measured delay (us): 198
          

Example 4  times a process that needs to be measured in milliseconds. Even for a microcontroller running an interpreted language (MicroPython) this is a simple task with a high degree of accuracy.


Example 4:
# Program that uses methods from the MicroPython 'time' library
# to time a coded 200 ms delay.

import time

# Time a delay of 200 milliseconds
start = time.ticks_ms()
time.sleep_ms(200)
finish = time.ticks_ms()
interval = time.ticks_diff(finish, start)
print('Coded delay (ms):', 200)
print('Measured delay (ms):', interval)

Sample Output:
Coded delay (ms): 200
Measured delay (ms): 200