⏱️ Timer در میکروپایتون: چند کار هم‌زمان روی رزبری پای پیکو بدون sleep

✍️ تیم ووکوی فارسی ·

تا حالا هر برنامه‌ای که نوشته‌ایم با time.sleep صبر می‌کرد. این برای یک LED چشمک‌زن عالی است، ولی کافی است بخواهی دو چراغ با دو سرعت مختلف چشمک بزنند و هم‌زمان یک سنسور هم خوانده شود تا ببینی sleep همه‌چیز را قفل می‌کند. در این آموزش یاد می‌گیری با Timer در میکروپایتون و الگوی ticks_ms روی رزبری پای پیکو چند کار را هم‌زمان پیش ببری. این همان مفهومی است که در آردوینو به آن «millis به جای delay» می‌گویند.

مشکل sleep کجاست؟

این کد را ببین. می‌خواهیم LED اول هر ۲۰۰ میلی‌ثانیه و LED دوم هر ۷۰۰ میلی‌ثانیه عوض شود:

while True:
    led_fast.toggle()
    time.sleep_ms(200)
    led_slow.toggle()
    time.sleep_ms(700)

نتیجه؟ هر دو LED با هم یک چرخه‌ی ۹۰۰ میلی‌ثانیه‌ای دارند و هیچ‌کدام سرعت درستش را ندارد. در طول هر sleep پردازنده واقعاً «خواب» است: دکمه خوانده نمی‌شود، نمایشگر به‌روز نمی‌شود و هیچ کار دیگری انجام نمی‌شود. راه حل این است که به جای «صبر کن»، بپرسیم «آیا وقتش رسیده؟».

چه چیزی می‌سازیم؟

دو LED بیرونی که با دو سرعت کاملاً مستقل چشمک می‌زنند، یک شمارنده‌ی ثانیه که در سریال مانیتور چاپ می‌شود، و LED داخلی برد که سه ثانیه بعد از روشن شدن خودش خاموش می‌شود؛ همه هم‌زمان و بدون این‌که حلقه‌ی اصلی جایی معطل بماند.

قطعه‌ها و اتصال‌ها

قطعهپایه‌ی پیکو
LED سریع (از راه مقاومت ۲۲۰ اهم)GP15، پایه‌ی فیزیکی ۲۰
LED کند (از راه مقاومت ۲۲۰ اهم)GP14، پایه‌ی فیزیکی ۱۹
کاتد هر دو LEDGND
LED داخلیGP25 روی پیکوی معمولی (روی Pico W بنویس Pin("LED"))

روش اول: machine.Timer

تایمر یک ساعت زنگ‌دار سخت‌افزاری است: به آن می‌گویی «هر ۲۰۰ میلی‌ثانیه این تابع را صدا بزن» و خودش، مستقل از حلقه‌ی اصلی، این کار را انجام می‌دهد.

from machine import Pin, Timer
import time

led_fast = Pin(15, Pin.OUT)
led_slow = Pin(14, Pin.OUT)
onboard = Pin(25, Pin.OUT)

seconds = 0

def blink_fast(t):
    led_fast.toggle()

def blink_slow(t):
    led_slow.toggle()

def count_second(t):
    global seconds
    seconds += 1

t1 = Timer(period=200, mode=Timer.PERIODIC, callback=blink_fast)
t2 = Timer(period=700, mode=Timer.PERIODIC, callback=blink_slow)
t3 = Timer(freq=1, mode=Timer.PERIODIC, callback=count_second)

# One-shot timer: switch the on-board LED off 3 s after start
onboard.on()
t4 = Timer(period=3000, mode=Timer.ONE_SHOT, callback=lambda t: onboard.off())

shown = -1
while True:
    if seconds != shown:          # heavy work (printing) stays in the main loop
        shown = seconds
        print("uptime:", shown, "s")
    time.sleep_ms(10)

توضیح خط‌های مهم

برای متوقف کردن یک تایمر بنویس t1.deinit(). اگر برنامه را در Thonny با Stop قطع کنی، تایمرهای PERIODIC ممکن است هنوز روی برد واقعی کار کنند و LED همچنان چشمک بزند؛ با یک ری‌استارت نرم (Ctrl+D) یا deinit خاموششان کن.

روش دوم: الگوی ticks_ms در حلقه‌ی اصلی

اگر کارهایت زیاد یا کمی سنگین‌اند (مثل خواندن سنسور DHT یا نوشتن روی LCD)، روش ساده‌تر و امن‌تر این است که در خود حلقه‌ی اصلی، زمان را بپرسی. این دقیقاً همان الگوی millis() در آردوینو است:

from machine import Pin
import time

led_fast = Pin(15, Pin.OUT)
led_slow = Pin(14, Pin.OUT)

FAST_MS = 200
SLOW_MS = 700
last_fast = time.ticks_ms()
last_slow = time.ticks_ms()

while True:
    now = time.ticks_ms()

    if time.ticks_diff(now, last_fast) >= FAST_MS:
        last_fast = time.ticks_add(last_fast, FAST_MS)
        led_fast.toggle()

    if time.ticks_diff(now, last_slow) >= SLOW_MS:
        last_slow = time.ticks_add(last_slow, SLOW_MS)
        led_slow.toggle()

    time.sleep_ms(1)

کدام روش را انتخاب کنم؟

machine.Timerticks_ms در حلقه
دقت زمان‌بندیخیلی خوب، مستقل از حلقه‌ی اصلیخوب، به شرطی که حلقه جایی گیر نکند
کار سنگین مجاز است؟نه، تابع باید خیلی کوتاه باشدبله
سادگی و اشکال‌زداییمتوسطساده
مناسب برایچشمک، شمارش، نمونه‌برداری منظمبیشتر پروژه‌ها: سنسور، نمایشگر، منو

در عمل خیلی وقت‌ها هر دو را با هم به کار می‌بری: تایمر یک پرچم را روشن می‌کند و حلقه‌ی اصلی کار اصلی را انجام می‌دهد. روی برد واقعی برای پروژه‌های بزرگ‌تر ماژول asyncio هم هست که چند «وظیفه» را با await کنار هم اجرا می‌کند؛ ولی برای شروع، همین دو روش کافی‌اند.

اجرا در شبیه‌ساز

در تینکرکد فارسی مثال چشمک‌زن با MicroPython روی پیکو را باز کن، یک LED دیگر با مقاومت روی GP14 اضافه کن و هر کدام از دو کد بالا را جای کد مثال بگذار. شبیه‌ساز میکروپایتون واقعی را اجرا می‌کند، پس Timer، ticks_ms و ticks_diff همان رفتار برد را دارند و چاپ‌ها در سریال مانیتور می‌آیند. پیکو و میکروپایتون جزو اشتراک پلاس هستند: مثال برای خواندن رایگان است و برای اجرا پلاس لازم داری. اگر می‌خواهی همین مفهوم را رایگان روی آردوینو تمرین کنی، درس چند کار هم‌زمان و پروژه‌ی چند کار هم‌زمان با آردوینو را ببین.

اشکال‌های رایج

تایمر خواهر دوقلوی وقفه است؛ اگر هنوز وقفه‌ی دکمه را ندیده‌ای، دکمه و وقفه با میکروپایتون را بخوان. برای مرور پایه‌های زبان هم آموزش میکروپایتون از صفر و صفحه‌ی میکروپایتون را ببین.

تایمر پیکو را در شبیه‌ساز امتحان کن ⏱️

سؤال‌های رایج

فرق Timer.PERIODIC با Timer.ONE_SHOT چیست؟

تایمر PERIODIC تا وقتی متوقفش نکنی، هر بار بعد از گذشت period تابع را دوباره صدا می‌زند. ONE_SHOT فقط یک بار، بعد از همان مدت، تابع را اجرا می‌کند و خودش خاموش می‌شود.

چند Timer می‌توانم روی پیکو بسازم؟

روی پورت rp2 تایمرها مجازی‌اند و می‌توانی چندتا با Timer() بسازی. ولی هر تایمر یک وقفه است؛ اگر کارهای زیادی داری، معمولاً یک تایمر یا الگوی ticks_ms در حلقه‌ی اصلی ساده‌تر و قابل‌اعتمادتر است.

چرا نباید در تابع Timer از sleep یا print زیاد استفاده کنم؟

تابع تایمر شبیه تابع وقفه است و وسط کار برنامه‌ی اصلی اجرا می‌شود. اگر طول بکشد، بقیه‌ی کارها عقب می‌افتند. در آن فقط یک متغیر یا پرچم را عوض کن و کار سنگین را در حلقه‌ی اصلی انجام بده.

مقاله‌های مرتبط

🛠️

نصب میکروپایتون روی رزبری پای پیکو و راه‌اندازی Thonny، قدم‌به‌قدم

⚙️

برنامه‌نویسی رزبری پای پیکو با زبان آردوینو (C++ و هسته‌ی arduino-pico)

⚖️

میکروپایتون یا C++ آردوینو؟ مقایسه‌ی کامل برای انتخاب زبان میکروکنترلر

🔢

کیپد ۴×۴ با آردوینو: طرز کار ماتریسی، کتابخانه‌ی Keypad و قفل رمزدار با سروو

همه‌ی آموزش‌های رزبری پای پیکو و میکروپایتون ← · همه‌ی مقاله‌ها