National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 07

計時:不讓程式卡住

sleep 的問題、ticks 計時與 Timer
Interaction 25 minute(s)

Learning Objectives

  • 說明 time.sleep 為什麼會讓程式錯過按鈕或感測器的變化
  • 用 ticks_ms 與 ticks_diff 判斷「時間到了沒」,寫出不卡住的閃燈
  • 說明為什麼比較時間要用 ticks_diff,不能直接相減
  • 用 machine.Timer 定期執行函式,並知道回呼函式要寫得短

睡著的程式什麼都做不了

到目前為止,控制時間的方法只有一個:time.sleep()。它很好懂,但程式在 sleep 的時候完全停在那一行, 不會去看按鈕、不會讀感測器,這叫阻塞(blocking)。

只做一件事的時候沒問題。可是真的作品通常要同時處理好幾件事:

  • 指示燈每 0.5 秒閃一次
  • 隨時注意按鈕有沒有被按
  • 每 2 秒讀一次溫度,每 10 秒上傳一次

如果每件事都用 sleep 等,等待的時間會加在一起,程式反應變慢,短暫的輸入也會被漏掉。下一個範例就是這種情況。

逐步看:sleep 讓程式錯過按鈕

按鈕在 1.2 秒按下、1.5 秒放開(看上方的按鈕)。程式每 1 秒才醒來一次看按鈕, 你可以拖到第 1.2 秒附近:那時程式停在 time.sleep(1) 裡。

不要等,改成「看時間到了沒」

換一個想法:迴圈跑得很快,每一圈都看一下時鐘,時間到了才做事,沒到就跳過,繼續處理別的事。

last = time.ticks_ms()               # 記下上一次做事的時間
while True:
    now = time.ticks_ms()
    if time.ticks_diff(now, last) >= 500:   # 距離上次滿 500 ms 了嗎?
        做事()
        last = now
    處理其他事()                      # 每一圈都會執行到
函式 作用
time.ticks_ms() 從某個起點開始經過的毫秒數(MicroPython 專有;另有 ticks_us() 以微秒計)
time.ticks_diff(a, b) a 比 b 晚多少,結果可以是負數
time.ticks_add(t, 毫秒) t 之後(或之前)幾毫秒的時間點,用來算期限

為什麼不直接相減

ticks_ms() 的數字不會一直變大:到了上限會回繞(wrap around)重新從 0 開始,就像時鐘走過 12 點又回到 1 點。 上限多少依開發板而定,MicroPython 只保證是 2 的次方。剛好跨過上限時,now - last 會算出一個很大的負數, 程式就以為時間倒流了。ticks_diff() 會正確處理回繞,所以官方文件要求:比較 ticks 一律用 ticks_diff, 不要用 -、<、> 直接比。

設定期限的寫法也一樣:

deadline = time.ticks_add(time.ticks_ms(), 200)      # 200 ms 之後
while time.ticks_diff(deadline, time.ticks_ms()) > 0:
    做一點事()

time.sleep_ms(10) 這種很短的等待仍然可以用:它讓迴圈不會空轉,耗電較少,反應也只慢 10 ms。

逐步看:一邊閃燈、一邊看按鈕

按鈕一樣在 1.2 秒左右按下。這次迴圈每 0.1 秒跑一圈,每一圈都看按鈕;LED 則是看時間到了才切換。 跳到第 30 步附近,看 now 與 last_blink 的差什麼時候達到 500。

讓硬體幫你計時:machine.Timer

另一種做法是交給硬體計時器:設定好間隔,時間到了硬體就呼叫你的函式,主程式完全不用管,和第 04 課的中斷很像。

from machine import Timer

tim = Timer(0)
tim.init(period=500, mode=Timer.PERIODIC, callback=tick)   # 每 500 ms 呼叫 tick
tim.init(period=2000, mode=Timer.ONE_SHOT, callback=done)  # 2 秒後呼叫一次 done
tim.deinit()                                                # 停止
  • Timer.PERIODIC:每隔 period 毫秒重複呼叫;Timer.ONE_SHOT:只呼叫一次。
  • 原版 ESP32 有 4 個硬體計時器,編號 0~3。有些開發板(例如 Raspberry Pi Pico)可以寫 Timer(-1) 使用軟體計時器,ESP32 的 MicroPython 目前不支援,請用 0~3。
  • 回呼函式的規則和中斷處理函式一樣:要短,改計數或旗標就好,複雜的事交給主程式。

兩種做法怎麼選?要處理的事情少、時間要求不嚴格時,ticks_ms 寫在主迴圈裡最好懂,也比較好除錯; 需要固定節奏、不想讓主迴圈的快慢影響時,用 Timer。

逐步看:計時器在背景閃燈

主程式只是每秒印一行,從來沒有切換 LED;每 500 ms,程式會跳進 tick 函式執行一次。比較 ticks 增加的速度與主程式印出的次數。

互動練習:同時做三件事

電路上有紅燈(GPIO23)、綠燈(GPIO22)、黃燈(GPIO21)與按鈕(GPIO4,上拉接法)。 起始程式用 sleep 讓紅燈每秒閃一次。依序完成任務:

  1. 直接執行起始程式。
  2. 紅燈維持每 1 秒閃一次(亮 0.5 秒、暗 0.5 秒),同時讓綠燈每 0.6 秒閃一次(亮 0.3 秒、暗 0.3 秒)。用 sleep 很難做到,試試 ticks_ms,或兩個 Timer。
  3. 兩個燈繼續閃的同時,黃燈要跟著按鈕:按住就亮,放開就暗。可以在「輸入設定」改按鈕按下的時間測試。
  4. 回答一個問題。

小測驗:阻塞與 ticks

1.

迴圈裡有 time.sleep(2),按鈕只在每圈開頭讀一次。使用者按了 0.3 秒就放開,最可能發生什麼事?

2.

下面模擬「時間到了才做事」的迴圈,times 是每一圈讀到的 ticks_ms()。會印出什麼?

times = [0, 120, 260, 390, 510, 640, 770, 900, 1030]
last = 0
for now in times:
    if now - last >= 500:
        print(now, end=" ")
        last = now
3.

為什麼 MicroPython 官方文件要求用 ticks_diff() 比較兩個 ticks 值?

第 1 次作答

小測驗:Timer

1.

tim.init(period=250, mode=Timer.PERIODIC, callback=tick) 之後主程式 time.sleep(2),這段時間 tick 大約被呼叫幾次?

2.

在 ESP32 的 MicroPython 上,下面哪一行不能用?

第 1 次作答

重點整理

  • time.sleep() 會阻塞:睡著的時候程式看不到按鈕、讀不到感測器。
  • 非阻塞的寫法:迴圈每一圈讀 ticks_ms(),用 ticks_diff(now, last) >= 間隔 判斷時間到了沒,到了才做事並更新 last。
  • ticks 的值到上限會回繞,比較一律用 ticks_diff(),算期限用 ticks_add()。
  • machine.Timer:init(period=…, mode=Timer.PERIODIC 或 ONE_SHOT, callback=…) 讓硬體定時呼叫函式,deinit() 停止;ESP32 用 0~3 號計時器,不支援 Timer(-1)。
  • 中斷與計時器的回呼函式都要短,重的工作交給主程式。

下一課控制 NeoPixel 全彩燈條,動畫效果正好用得上這一課的計時方法。

這一課完成了!
Next lesson:NeoPixel 全彩燈條
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。