time

time 模块管三件事:延时、计时、取当前时间。

  • 延时:让程序停一会儿,最常用
  • 计时:量一段代码跑了多久、判断两次动作间隔多久(按键防抖、测转速、倒计时都靠它)
  • 取时间:读日期和钟点,Pico 断电后时间会丢,开机默认从 2021-01-01 起算,要准时间得配 RTC 或联网对时

import time 就能用。MicroPython 里也能写 import utime,两者是同一个模块(u 是兼容 CPython 的老写法),初学统一写 time 就好。


基本用法

# ============ 接线 ============
# 演示 time 模块的用法,只用到串口输出,不用接线
# =============================
import time

# 延时(秒)
time.sleep(1)        # 停 1 秒

# 延时(毫秒)
time.sleep_ms(500)   # 停 500 毫秒

# 延时(微秒)
time.sleep_us(100)   # 停 100 微秒

# 取当前时间戳(自 2000-01-01 起算的秒数)
print(time.time())

# 取当前时间元组,按年/月/日/时/分/秒顺序取
t = time.localtime()
print(t)

延时函数

函数 说明 精度
sleep(seconds) 延时指定秒数 秒
sleep_ms(ms) 延时指定毫秒数 毫秒
sleep_us(us) 延时指定微秒数 微秒
# ============ 接线 ============
# 只用到串口输出,不用接线
# =============================
from machine import Pin
import time

led = Pin(25, Pin.OUT)   # 板载 LED(Pico W 写 Pin('LED'))

while True:
    led.on()             # 点亮
    time.sleep(1)        # 亮 1 秒
    led.off()            # 熄灭
    time.sleep_ms(500)   # 灭 0.5 秒
    # 想闪得更快,把上面两个延时一起改小

三个函数单位不同,用错就差 1000 倍:想停 0.5 秒,写 time.sleep(0.5),别写 time.sleep_ms(0.5)(那等于没停)。


计时函数

sleep 是"停多久",ticks_* 是"过了多久"。*计时一律用 `ticks_,不要用time.time()`**,原因见下面表格。

函数 返回什么 单位
ticks_ms() 开机到现在的毫秒数 毫秒
ticks_us() 开机到现在的微秒数 微秒
ticks_diff(t2, t1) 两个时刻之间差多少 跟传入值同单位
# ============ 接线 ============
# 只用到串口输出,不用接线
# =============================
import time

# 毫秒计时:量一段代码跑多久
start = time.ticks_ms()          # 记下起点(毫秒)
time.sleep(0.2)                  # 假装干点活(这里用延时占用 200ms)
cost = time.ticks_diff(time.ticks_ms(), start)   # 终点减起点
print('耗时', cost, '毫秒')       # 大约 200 毫秒

# 微秒计时:量更短的动作
start = time.ticks_us()          # 记下起点(微秒)
time.sleep_us(500)               # 占用 500 微秒
cost = time.ticks_us()
print('耗时', time.ticks_diff(cost, start), '微秒')

为什么不用 time.time() 计时? 看这张表就明白了:

time.time() ticks_ms() / ticks_us()
返回什么 日期时间(秒,带小数) 开机以来经过的毫秒/微秒
受系统调时影响 会(对时后可能倒退) 不会,一直向前走
算时间差 自己写减法,浮点数 用 ticks_diff(),结果精确到整毫秒
典型用途 读日期、钟点 防抖、测转速、倒计时、超时判断

ticks_diff(t2, t1) 的写法是「新时刻减旧时刻」,顺序写反会得到一个很大的数(或者负数),这是最常见的错。


用法一:记录时长(量代码跑多久)

# ============ 接线 ============
# 只用到串口输出,不用接线
# =============================
import time

start = time.ticks_ms()          # 开始计时

# 待测的代码:累加 1 到 100000
total = 0
for i in range(100001):
    total += i

elapsed = time.ticks_diff(time.ticks_ms(), start)   # 已经过去多少毫秒
print('累加结果:', total)
print('执行耗时:', elapsed, '毫秒')

想量更短的动作就换 ticks_us(),写法一样,把单位换成微秒即可。


用法二:判断间隔(按键防抖)

真实场景里最常用的形态:记下上次的时刻,和当前时刻比一比,够久了才算新动作。

# ============ 接线 ============
# 按键一脚 → Pico GP14
# 按键另一脚 → Pico GND(用内部上拉,无需外接电阻)
# =============================
from machine import Pin
import time

key = Pin(14, Pin.IN, Pin.PULL_UP)   # 内部上拉:松开读 1,按下读 0

DEBOUNCE = 50                        # 防抖时间:50 毫秒内的抖动都算同一次
last_ms = 0                          # 上一次有效按下的时刻
last_state = 1                       # 上一次读到的状态

print('按键防抖检测')

while True:
    now = time.ticks_ms()            # 当前时刻(毫秒)
    state = key.value()              # 当前读到的高低电平

    # 状态变了,并且距上次动作超过 50ms,才算一次有效动作
    if state != last_state and time.ticks_diff(now, last_ms) > DEBOUNCE:
        if state == 0:
            print('按键按下')
        else:
            print('按键释放')
        last_ms = now                # 记下这一次的时刻,供下次比

    last_state = state               # 更新状态
    time.sleep_ms(10)                # 主循环节奏

例子里的 last_ms = 0 是"刚开机的时刻":第一次按下时 ticks_diff(now, 0) 就是开机至今的毫秒数,肯定大于 50,所以第一次一定能触发。


用法三:倒计时(算还剩多少)

# ============ 接线 ============
# 数码管 VCC → Pico 3V3
# 数码管 GND → Pico GND
# 数码管 CLK → Pico GP16
# 数码管 DIO → Pico GP17
# 模块要上传 tm1637.py
# =============================
from machine import Pin
import tm1637
import time

tm = tm1637.TM1637(clk=Pin(16), dio=Pin(17))   # 建数码管对象

TOTAL = 30000                                  # 倒计时总长(毫秒),这里 30 秒
start = time.ticks_ms()                        # 记下开始时刻

while True:
    # 已经过去多少毫秒
    elapsed = time.ticks_diff(time.ticks_ms(), start)
    # 还剩多少秒(整除 1000,不足 1 秒就进位)
    left = (TOTAL - elapsed + 999) // 1000
    if left <= 0:
        tm.show('0000')                        # 时间到
        break
    tm.number(left)                            # 显示剩余秒数
    time.sleep_ms(100)                         # 每 100ms 刷新一次

print('倒计时结束')

tm.number(30000) 和 tm.show('0000') 是 tm1637 库的两个常用方法。 这里的 + 999 是"不足一秒也算一秒",让显示从 30 开始往下走。


取当前时间

# ============ 接线 ============
# 只用到串口输出,不用接线
# =============================
import time

# 时间戳:自 2000-01-01 起算的秒数
t = time.time()
print('时间戳:', t)

# time() 和 ticks_ms() 的关系:毫秒 = 秒 × 1000 + 毫秒余数
print('等价毫秒:', time.time() * 1000)

# 本地时间(元组):(年, 月, 日, 时, 分, 秒, 星期, 一年第几天)
t = time.localtime()
print('本地时间:', t)
print('年: %d, 月: %d, 日: %d' % (t[0], t[1], t[2]))
print('时: %d, 分: %d, 秒: %d' % (t[3], t[4], t[5]))
print('星期: %d(0=周一)' % t[6])

时间元组的顺序要记牢:前 6 位是 年 月 日 时 分 秒,第 7 位是星期(0 表示周一),第 8 位是"一年中的第几天"。 别写成 t[0]=日 这种想当然的顺序,取错了不报错,只是数字莫名其妙。


时间格式化

# ============ 接线 ============
# 只用到串口输出,不用接线
# =============================
import time

t = time.localtime()

# %Y 年  %m 月  %d 日  %H 时  %M 分  %S 秒
print(time.strftime('%Y-%m-%d', t))             # 2021-01-01
print(time.strftime('%H:%M:%S', t))             # 00:00:01
print(time.strftime('%Y-%m-%d %H:%M:%S', t))    # 2021-01-01 00:00:01
print(time.strftime('%m月%d日 %H:%M', t))       # 01月01日 00:00

不传第二个参数也是用"当前时间":time.strftime('%H:%M') 等于 time.strftime('%H:%M', time.localtime())。


示例:LCD1602 时钟

# ============ 接线 ============
# LCD1602(I2C)VCC → Pico 3V3
# LCD1602 GND → Pico GND
# LCD1602 SDA → Pico GP8
# LCD1602 SCL → Pico GP9
# 模块要上传 lcd_api.py 和 pico_i2c_lcd.py
# =============================
from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)   # I2C0 固定用 GP8/GP9
lcd = I2cLcd(i2c, 0x27, 2, 16)                       # 地址 0x27,2 行 16 字

while True:
    t = time.localtime()                             # 取当前时间
    lcd.clear()                                      # 清屏
    lcd.putstr(time.strftime('%H:%M:%S', t))         # 第一行显示钟点
    lcd.move_to(0, 1)                                # 光标挪到第二行
    lcd.putstr(time.strftime('%Y-%m-%d', t))         # 第二行显示日期
    time.sleep(1)                                    # 每秒刷新

注意事项

  • 单位别混:sleep(0.5) 是半秒,sleep_ms(500) 才是 500 毫秒;sleep_ms(0.5) 等于没停
  • *计时用 `ticks_**:time.time()` 是日期时间(浮点秒),对时后会跳变,不适合测间隔
  • ticks_diff 顺序:写法是 ticks_diff(新时刻, 旧时刻),写反得负数或大数
  • 时间起点:Pico 没有电池,断电后时间丢失,开机默认从 2021-01-01 开始,取到的日期是假的
  • 要准时间:需接 RTC 模块(CR2032 电池)或联网对时(Pico W 走 NTP)
  • 长时间只看毫秒:ticks_ms() 约 6 天多就会溢出回绕,短项目够用;跨天计时用 time.time()

常见问题

Q: ticks_diff 算出来是负数,或者是个特别大的数? A: 参数顺序写反了。正确写法是 time.ticks_diff(现在, 过去)——从旧时刻减去新时刻必然为负。

Q: 想一次停 0.5 秒,写成 time.sleep_ms(0.5) 没反应? A: sleep_ms 收的是毫秒整数,0.5 会被当成 0。要么写 time.sleep_ms(500),要么写 time.sleep(0.5)。

Q: time.localtime() 读出来年份是 2021,日期不对? A: 正常现象。Pico 没有电池供电的实时时钟,断电重启后从固件默认时间(2021-01-01)重新起算。要显示真实日期得接 RTC 模块或联网对时。

Q: 按键一次被识别成好几次? A: 机械按键按下、松开的瞬间会抖几下,用「用法二」的 ticks_diff 防抖:50ms 内的抖动只算一次。

Q: 中断函数里能用 time.sleep_ms() 吗? A: 不要。中断里不刷屏、不 sleep;中断只适合记录时刻、改计数这类立刻返回的操作(可参考滚珠开关页的做法)。