3.2. 計時

time 模組彙整了用於睡眠(將指令碼暫停一段已知時間)以及量測某件事耗時多久的函式。在微控制器上,這些並非可有可無的功能;它們正是指令碼用來掌握與外界互動節奏的方法 —— 一個接腳要維持高準位多久、取樣之間要等待多久、自按鈕上次被按下以來過了多久。

3.2.1. 睡眠

有三個睡眠函式會將指令碼阻塞所要求的時間:

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 確實較晚,負值表示它在過去。此函式會在內部處理翻轉。

小訣竅

請始終把 time.ticks_ms() / time.ticks_us()time.ticks_diff() 搭配使用。原始的減法在 大多數 時候是正確的;它出錯的時候正是指令碼已經執行了很長一段時間之時 —— 而那通常是除錯計時問題最糟糕的時機。

3.2.4. 非阻塞計時

微控制器指令碼通常有不只一件事要「同時」處理:讀取按鈕、閃爍 LED、輪詢感測器、服務 UART。sleep 不適合做這件事 —— 當一個 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 讀取逾時。同樣的三個函式(time.ticks_ms()time.ticks_diff()time.ticks_add())涵蓋了每一種情況。