3.2. タイミング

time モジュールは、スリープ(既知の時間だけスクリプトを一時停止する)するための関数と、何かにどれだけ時間がかかるかを測定するための関数をまとめたものです。マイクロコントローラでは、これらはあれば嬉しいという程度のものではありません。これらこそが、スクリプトが外界とのやり取りのペースを調整する方法、すなわちピンをどれだけの間 high に保つか、サンプリングの間隔をどれだけ空けるか、ボタンが最後に押されてからどれだけ経ったかを測る方法なのです。

3.2.1. スリープ

3 つのスリープ関数が、要求された時間だけスクリプトをブロックします。

  • time.sleep(s) -- s 秒間一時停止します。浮動小数点数を受け取るので、time.sleep(0.5) は 0.5 秒待ちます。

  • time.sleep_ms(ms) -- ms ミリ秒間一時停止します。引数は整数でなければなりません。

  • time.sleep_us(us) -- us マイクロ秒間一時停止します。

import time

print("now")
time.sleep_ms(500)
print("half a second later")

典型的な「少し待つ」必要には time.sleep_ms() を使い、タイミングがタイトでなければならないときにのみ time.sleep_us() を使ってください。通常の time.sleep() でも問題ありませんが、整数引数のバリアントは浮動小数点変換を避けられ、短い間隔ではより自然に読めます。

スリープは ブロッキング 呼び出しです。スクリプトがスリープしている間、それは他に何もしていません。ピンを読まず、UART を処理せず、LED を更新しません。ブロックすることがまさに望みであるときには sleep を使い、そうでないときには下記のノンブロッキングパターンを使ってください。

3.2.2. クロックの読み取り

あるコードがどれだけ時間がかかるかを測定するには、その前後でクロックを読み取ります。

import time

start = time.ticks_ms()
do_work()
elapsed = time.ticks_ms() - start
print("took", elapsed, "ms")

これは ほとんどの 場合うまくいきます。ティックカウンタは、大きいながらも有限の数のティックの後にロールオーバー(ゼロに戻る)し、そのラップをまたいだ単純な減算は、とてつもなく誤った負または正の数を生み出します。

0 から MAX までの数直線で、開始ティックが MAX 付近、 終了ティックが 0 付近にあるもの。破線のラップアラウンド 矢印が、MAX の後にカウンタが 0 に戻ることを示している。

ティックカウンタは整数の上限に達するとゼロに戻ります。そのラップをまたいだ単純な減算は誤りです。

3.2.3. ticks_diff

ラップをまたいでも経過ティックを正しく取得するには、time.ticks_diff() を使ってください。

import time

start = time.ticks_ms()
do_work()
elapsed = time.ticks_diff(time.ticks_ms(), start)
print("took", elapsed, "ms")

引数の順序は ticks_diff(later, earlier) です。この式は「laterearlier からどれだけ後か」と読みます。結果は符号付き整数で、正なら later が実際に後であることを、負なら過去にあることを意味します。この関数はラップを内部で処理します。

Tip

time.ticks_ms() / time.ticks_us() は常に time.ticks_diff() と組み合わせてください。生の減算は ほとんどの 場合は正しいのですが、誤るのはスクリプトが長時間動作しているときであり、それはたいていタイミングの不具合をデバッグするには最悪のタイミングです。

3.2.4. ノンブロッキングタイミング

マイクロコントローラのスクリプトは通常、ボタンを読む、LED を点滅させる、センサーをポーリングする、UART を処理するなど、複数のことを「同時に」行う必要があります。sleep はそれには向いていません。1 つの sleep が動作している間、他のタスクは停止してしまうからです。

標準的なパターンは、タスクごとに デッドライン を保持し、各ループでそのデッドラインを過ぎたかどうかをチェックすることです。動作する判断は time.ticks_diff() の上に構築され、sleep には決して依存しません。

import time

next_blink = time.ticks_ms()
next_poll  = time.ticks_ms()
state = False

while True:
    now = time.ticks_ms()

    if time.ticks_diff(now, next_blink) >= 0:
        state = not state
        # update LED here
        next_blink = time.ticks_add(next_blink, 500)

    if time.ticks_diff(now, next_poll) >= 0:
        # read sensor here
        next_poll = time.ticks_add(next_poll, 100)

LED は 500 ms ごとにトグルし、センサーは 100 ms ごとにポーリングされ、どちらのタスクも他方をブロックしません。time.ticks_add() はラップに引っかかることなく、既知の増分だけデッドラインを進めます。

この形、すなわち単一のループ、いくつかの時間制御されたタスク、本体に sleep がないというものは、ハードウェアコードの至るところに現れます。スイッチのソフトウェアデバウンス、モーターのシーケンス制御、UART の読み取りタイムアウトなどです。同じ 3 つの関数(time.ticks_ms()time.ticks_diff()time.ticks_add())があらゆるケースをカバーします。