3.2. การจับเวลา

โมดูล time รวมฟังก์ชันสำหรับการหลับ (หยุดสคริปต์เป็นระยะเวลาที่กำหนด) และสำหรับวัดว่าบางสิ่งใช้เวลานานเท่าใด บนไมโครคอนโทรลเลอร์สิ่งเหล่านี้ไม่ใช่สิ่งที่มีก็ดี แต่เป็นวิธีที่สคริปต์กำหนดจังหวะการโต้ตอบกับโลกภายนอก -- ต้องดึงพินให้สูงนานเท่าใด, รอระหว่างตัวอย่างนานเท่าใด, นานเท่าใดนับตั้งแต่กดปุ่มครั้งล่าสุด

3.2.1. การหลับ

ฟังก์ชันหลับสามตัวบล็อกสคริปต์เป็นระยะเวลาที่ร้องขอ:

  • time.sleep(s) -- หยุดเป็นเวลา s วินาที รับ float ดังนั้น 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() แบบธรรมดาก็ใช้ได้เช่นกัน แต่ตัวแปรที่รับอาร์กิวเมนต์จำนวนเต็มหลีกเลี่ยงการแปลงทศนิยมและอ่านได้เป็นธรรมชาติมากกว่าสำหรับช่วงเวลาสั้น

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 จะ roll over (ย้อนกลับเป็นศูนย์) หลังจาก tick จำนวนมากแต่จำกัด และการลบแบบธรรมดาข้าม wrap นั้นให้ผลลัพธ์จำนวนลบหรือบวกที่ผิดพลาดอย่างมาก

Number line from 0 to MAX with a start tick near MAX and an end tick near 0; a dashed wrap-around arrow shows that after MAX the counter returns to 0.

ตัวนับ tick จะย้อนกลับเป็นศูนย์เมื่อถึงขีดจำกัดจำนวนเต็ม การลบแบบธรรมดาข้าม wrap นั้นผิด

3.2.3. ticks_diff

เพื่อรับ tick ที่ผ่านไปอย่างถูกต้อง แม้ข้าม wrap ให้ใช้ 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 อยู่ในอนาคตจริงๆ ลบหมายความว่าอยู่ในอดีต ฟังก์ชันจัดการ wrap ภายใน

Tip

จับคู่ time.ticks_ms() / time.ticks_us() กับ time.ticks_diff() เสมอ การลบแบบดิบถูกต้อง ส่วนใหญ่ เวลาที่ผิดคือเมื่อสคริปต์ทำงานมาเป็นเวลานาน -- ซึ่งมักเป็นเวลาที่แย่ที่สุดในการดีบักปัญหาการจับเวลา

3.2.4. การจับเวลาแบบไม่บล็อก

สคริปต์ไมโครคอนโทรลเลอร์มักมีสิ่งที่ต้องทำ "ในเวลาเดียวกัน" มากกว่าหนึ่งอย่าง: อ่านปุ่ม, กะพริบ LED, poll เซนเซอร์, ให้บริการ 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 มิลลิวินาที เซนเซอร์ถูก poll ทุก 100 มิลลิวินาที และไม่มีงานใดบล็อกอีกงาน time.ticks_add() เลื่อนเส้นตายไปข้างหน้าด้วยค่าเพิ่มที่กำหนดโดยไม่ผิดพลาดจาก wrap

รูปแบบนี้ -- ลูปเดียว หลายงานจับเวลา ไม่มี sleep ในตัว -- ปรากฏทุกที่ที่มีโค้ดฮาร์ดแวร์: การ debounce ปุ่มด้วยซอฟต์แวร์, การจัดลำดับมอเตอร์, timeout การอ่าน UART ฟังก์ชันสามตัวเดิม (time.ticks_ms(), time.ticks_diff(), time.ticks_add()) ครอบคลุมทุกกรณี