3.2. 计时¶
time 模块汇集了用于睡眠(将脚本暂停一段已知时间)以及测量某件事耗时的函数。在微控制器上,这些不是可有可无的功能;它们正是脚本用来掌控与外部世界交互节奏的手段——把引脚保持高电平多久、两次采样之间等待多久、距离按钮上次被按下过去了多久。
3.2.1. 睡眠¶
三个睡眠函数会按请求的时长阻塞脚本:
time.sleep(s)—— 暂停s秒。接受浮点数,所以time.sleep(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. 读取时钟¶
要测量一段代码的耗时,在它前后各读取一次时钟:
time.ticks_ms()—— 当前以毫秒为单位的 tick 值。time.ticks_us()—— 同上,以微秒为单位。
import time
start = time.ticks_ms()
do_work()
elapsed = time.ticks_ms() - start
print("took", elapsed, "ms")
这在大多数时候是有效的。tick 计数器在经过一个很大但有限的 tick 数后会翻转(绕回到零),而对跨越这次翻转的两个值进行简单相减会得出一个大得离谱的负数或正数。
tick 计数器在达到整数上限时会绕回到零。跨越这次绕回的简单相减是错误的。¶
3.2.3. ticks_diff¶
要正确地获取经过的 tick 数,即使跨越绕回也能正确,请使用 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) —— 该表达式读作“later 在 earlier 之后有多远”。结果是一个有符号整数;正数表示 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())涵盖了所有情形。