مدیریت پین‌های GPIO در میکروپایتون: کنترل سنسورها و عملگرها

فهرست مطالب

مدیریت پین‌های GPIO در میکروپایتون: کنترل سنسورها و عملگرها

مدیریت کارآمد پین‌های ورودی/خروجی عمومی (GPIO) در توسعه سیستم‌های امبدد، سنگ بنای تعامل با دنیای فیزیکی است. میکروپایتون، با ترکیب سادگی و قدرت زبان پایتون با قابلیت‌های برنامه‌نویسی سطح پایین سخت‌افزار، ابزاری بی‌نظیر برای توسعه‌دهندگانی است که به دنبال پیاده‌سازی سریع و قابل اعتماد پروژه‌های اینترنت اشیا (IoT) و رباتیک هستند. این مقاله به بررسی عمیق و تخصصی نحوه مدیریت پین‌های GPIO در میکروپایتون می‌پردازد، از مفاهیم اولیه ورودی و خروجی دیجیتال گرفته تا پروتکل‌های ارتباطی پیچیده و ملاحظات پیشرفته، با هدف توانمندسازی مهندسان و برنامه‌نویسان برای کنترل دقیق سنسورها و عملگرها در کاربردهای واقعی.

در اکوسیستم رو به رشد میکروکنترلرها، توانایی برنامه‌ریزی و کنترل پین‌های GPIO برای تعامل با انواع اجزای الکترونیکی حیاتی است. این اجزا می‌توانند شامل سنسورهای محیطی برای جمع‌آوری داده‌ها، موتورها برای حرکت، نمایشگرها برای ارائه اطلاعات، و انواع سوئیچ‌ها و دکمه‌ها برای ورودی کاربر باشند. میکروپایتون، با ارائه یک API سطح بالا و در عین حال قدرتمند، فرآیند توسعه را به شدت ساده می‌کند و به برنامه‌نویسان اجازه می‌دهد تا به جای درگیر شدن با جزئیات سطح رجیستر، بر منطق کاربردی تمرکز کنند. این راهنمای جامع نه تنها جنبه‌های نظری را پوشش می‌دهد، بلکه با ارائه مثال‌های کد عملی و ملاحظات کاربردی، مسیر را برای پیاده‌سازی پروژه‌های پیچیده هموار می‌سازد.

تنوع سخت‌افزارهایی که میکروپایتون از آن‌ها پشتیبانی می‌کند، از جمله خانواده ESP32، ESP8266 و Raspberry Pi Pico (RP2040)، این پلتفرم را به یک انتخاب جذاب برای طیف وسیعی از کاربردها تبدیل کرده است. هر یک از این پلتفرم‌ها ویژگی‌های خاص خود را در زمینه GPIO، ADC، PWM و پروتکل‌های ارتباطی دارند که در این مقاله به آن‌ها اشاره خواهد شد. درک این تفاوت‌ها و نحوه بهره‌برداری از آن‌ها، کلید طراحی سیستم‌های بهینه و کارآمد است. از مدیریت دقیق زمان‌بندی PWM برای کنترل موتورها تا پیاده‌سازی پروتکل‌های I2C و SPI برای ارتباط با سنسورهای پیشرفته و نمایشگرها، هر جنبه‌ای از مدیریت GPIO در میکروپایتون با جزئیات تشریح خواهد شد.

علاوه بر این، مباحث پیشرفته‌تری مانند مدیریت وقفه‌ها (Interrupts) که برای پاسخگویی بلادرنگ به رویدادهای خارجی ضروری هستند، و همچنین ملاحظات مربوط به بهینه‌سازی مصرف انرژی و مدیریت خطا، به برنامه‌نویسان کمک می‌کند تا سیستم‌هایی پایدار، قابل اعتماد و با مصرف انرژی بهینه طراحی کنند. این مقاله با هدف تبدیل خواننده به یک متخصص در زمینه مدیریت GPIO با میکروپایتون، نه تنها دانش فنی لازم را فراهم می‌کند، بلکه دیدگاهی جامع نسبت به چالش‌ها و فرصت‌های موجود در طراحی سیستم‌های امبدد مدرن ارائه می‌دهد.

مقدمه‌ای بر GPIO و میکروپایتون در سیستم‌های امبدد

در قلب هر سیستم امبدد، پین‌های ورودی/خروجی عمومی (General Purpose Input/Output – GPIO) قرار دارند. این پین‌ها رابط اصلی بین میکروکنترلر و دنیای خارج هستند و به برنامه‌نویس اجازه می‌دهند تا سیگنال‌های دیجیتال را بخواند (ورودی) یا تولید کند (خروجی). مفهوم GPIO به ظاهر ساده است، اما توانایی برنامه‌ریزی دقیق این پین‌ها است که قدرت واقعی میکروکنترلرها را برای انجام وظایف پیچیده در محیط‌های مختلف آزاد می‌کند.

GPIO چیست و چرا اهمیت دارد؟

GPIOها پین‌های فیزیکی روی یک تراشه میکروکنترلر هستند که می‌توانند به صورت ورودی (برای خواندن وضعیت سنسورها، دکمه‌ها و غیره) یا خروجی (برای کنترل LEDها، رله‌ها، موتورها و غیره) پیکربندی شوند. این پین‌ها معمولاً قابلیت دریافت و ارسال سیگنال‌های دیجیتال با دو حالت منطقی (۰ یا ۱، LOW یا HIGH) را دارند. اهمیت GPIO در توانایی آن برای ایجاد ارتباط مستقیم بین نرم‌افزار اجرا شده روی میکروکنترلر و سخت‌افزار پیرامونی نهفته است. بدون GPIO، میکروکنترلر نمی‌توانست هیچ تعاملی با دنیای فیزیکی داشته باشد و به یک ماشین حساب ساده تبدیل می‌شد.

علاوه بر ورودی/خروجی دیجیتال ساده، بسیاری از پین‌های GPIO می‌توانند عملکردهای ویژه‌تری نیز داشته باشند، مانند:

  • پین‌های ADC (Analog-to-Digital Converter) برای خواندن ولتاژهای آنالوگ از سنسورها.
  • پین‌های PWM (Pulse Width Modulation) برای تولید سیگنال‌های آنالوگ شبیه‌سازی شده یا کنترل سرعت موتورها.
  • پین‌های پروتکل‌های ارتباطی مانند I2C، SPI و UART برای برقراری ارتباط با سنسورها، نمایشگرها و سایر میکروکنترلرها.
  • پین‌هایی که می‌توانند وقفه‌ها (Interrupts) را فعال کنند، به میکروکنترلر اجازه می‌دهند تا به رویدادهای خارجی فوراً پاسخ دهد.

میکروپایتون و نقش آن در مدیریت GPIO

میکروپایتون (MicroPython) یک پیاده‌سازی کم‌حجم و کارآمد از زبان برنامه‌نویسی پایتون ۳ است که به طور خاص برای اجرا روی میکروکنترلرها و سیستم‌های امبدد طراحی شده است. هدف اصلی میکروپایتون این است که توسعه سیستم‌های امبدد را با استفاده از سادگی و خوانایی پایتون، در عین حفظ دسترسی به قابلیت‌های سطح پایین سخت‌افزار، تسهیل کند. این ترکیب، میکروپایتون را به ابزاری قدرتمند برای نمونه‌سازی سریع و توسعه پروژه‌های پیچیده تبدیل کرده است.

با میکروپایتون، مدیریت پین‌های GPIO به طور چشمگیری ساده می‌شود. به جای نیاز به دانش عمیق از رجیسترهای سخت‌افزاری و بیت‌بندی، برنامه‌نویسان می‌توانند از ماژول machine و کلاس Pin برای پیکربندی و تعامل با پین‌ها استفاده کنند. این ماژول یک لایه انتزاعی ارائه می‌دهد که جزئیات خاص سخت‌افزار را پنهان می‌کند و کد را قابل حمل‌تر بین پلتفرم‌های مختلف میکروپایتون می‌سازد.

پلتفرم‌های پشتیبانی‌شده: ESP32، ESP8266، Raspberry Pi Pico و فراتر از آن

میکروپایتون از طیف وسیعی از میکروکنترلرها پشتیبانی می‌کند که هر یک دارای ویژگی‌های منحصر به فردی برای مدیریت GPIO هستند:

  • ESP32: یک میکروکنترلر بسیار قدرتمند از Espressif با قابلیت‌های Wi-Fi و Bluetooth داخلی. ESP32 تعداد زیادی پین GPIO، چندین ADC، PWM، I2C، SPI و UART را ارائه می‌دهد. این پلتفرم برای کاربردهای IoT پیچیده که نیاز به پردازش بالا و اتصال بی‌سیم دارند، ایده‌آل است. مدیریت پین در ESP32 بسیار انعطاف‌پذیر است، اما باید به برخی محدودیت‌های پین‌های خاص (مانند پین‌های فقط ورودی یا پین‌های متصل به حافظه فلش) توجه داشت.
  • ESP8266: برادر کوچک‌تر ESP32، که عمدتاً برای کاربردهای Wi-Fi ارزان قیمت طراحی شده است. ESP8266 پین‌های GPIO کمتری نسبت به ESP32 دارد و برخی محدودیت‌ها در استفاده از ADC و PWM وجود دارد. با این حال، برای پروژه‌های IoT ساده و کم‌هزینه که نیاز به اتصال Wi-Fi دارند، بسیار مناسب است.
  • Raspberry Pi Pico (RP2040): میکروکنترلر جدیدتر از بنیاد رزبری پای، با پردازنده دو هسته‌ای ARM Cortex-M0+ و مقدار زیادی SRAM. RP2040 دارای GPIOهای انعطاف‌پذیر، ADC با رزولوشن بالا، PWM و تعداد زیادی رابط SPI، I2C و UART است. قابلیت‌های PIO (Programmable I/O) آن امکان پیاده‌سازی پروتکل‌های سفارشی و زمان‌بندی‌های دقیق را فراهم می‌کند که در دیگر میکروکنترلرها کمتر دیده می‌شود.

با استفاده از میکروپایتون، برنامه‌نویسان می‌توانند با یک رویکرد یکپارچه، کد خود را روی این پلتفرم‌های متنوع پیاده‌سازی کنند، در حالی که از مزایای هر سخت‌افزار بهره می‌برند. این انعطاف‌پذیری و سهولت استفاده، میکروپایتون را به ابزاری قدرتمند در دستان توسعه‌دهندگان سیستم‌های امبدد تبدیل کرده است.

مبانی کار با پین‌های GPIO: ورودی و خروجی دیجیتال

اساسی‌ترین کاربرد پین‌های GPIO، پیکربندی آن‌ها به عنوان ورودی یا خروجی دیجیتال است. این قابلیت به میکروکنترلر اجازه می‌دهد تا اطلاعات را از محیط دریافت کند (مانند وضعیت یک دکمه) یا عملی را در محیط انجام دهد (مانند روشن/خاموش کردن یک LED).

پیکربندی پین به عنوان خروجی دیجیتال

هنگامی که یک پین به عنوان خروجی دیجیتال پیکربندی می‌شود، میکروکنترلر می‌تواند ولتاژ روی آن پین را کنترل کند. معمولاً این به معنای تنظیم پین روی سطح ولتاژ بالا (HIGH، معادل ۱ منطقی، مثلاً 3.3V یا 5V) یا سطح ولتاژ پایین (LOW، معادل ۰ منطقی، 0V) است. این کار برای روشن/خاموش کردن LEDها، فعال/غیرفعال کردن رله‌ها، یا ارسال سیگنال‌های کنترلی به سایر دستگاه‌های دیجیتال استفاده می‌شود.

در میکروپایتون، این کار با استفاده از ماژول machine و کلاس Pin انجام می‌شود. ابتدا باید یک شیء Pin ایجاد کنید و نوع آن را به عنوان خروجی (Pin.OUT) مشخص کنید.


from machine import Pin
import time

# پین 2 را به عنوان خروجی تعریف می کنیم. (مثال برای ESP32)
# شماره پین ممکن است بر اساس برد شما متفاوت باشد.
led_pin = Pin(2, Pin.OUT)

# یک حلقه برای چشمک زدن LED
while True:
    led_pin.value(1)  # پین را HIGH می کند (روشن می شود)
    print("LED روشن")
    time.sleep(0.5)   # 500 میلی ثانیه صبر می کند
    led_pin.value(0)  # پین را LOW می کند (خاموش می شود)
    print("LED خاموش")
    time.sleep(0.5)   # 500 میلی ثانیه صبر می کند

در کد بالا:

  • Pin(2, Pin.OUT) یک شیء Pin برای پین GPIO شماره ۲ ایجاد می‌کند و آن را به عنوان خروجی (Pin.OUT) تنظیم می‌کند.
  • متد value(1) پین را به حالت منطقی بالا (HIGH) می‌برد.
  • متد value(0) پین را به حالت منطقی پایین (LOW) می‌آورد.
  • متد time.sleep() اجرای برنامه را برای مدت مشخصی متوقف می‌کند.

پیکربندی پین به عنوان ورودی دیجیتال

وقتی یک پین به عنوان ورودی دیجیتال پیکربندی می‌شود، میکروکنترلر می‌تواند ولتاژ روی آن پین را بخواند و تعیین کند که آیا آن در حالت HIGH (۱) است یا LOW (۰). این قابلیت برای خواندن وضعیت دکمه‌ها، سوئیچ‌ها، یا خروجی‌های دیجیتال سنسورها استفاده می‌شود.

هنگام استفاده از پین‌ها به عنوان ورودی، مفهوم مقاومت‌های Pull-up و Pull-down بسیار مهم است. پین‌های ورودی شناور (Floating) می‌توانند در برابر نویز الکتریکی حساس باشند و مقادیر نامشخصی (نه ۱ و نه ۰ قطعی) را برگردانند. برای جلوگیری از این مشکل، از مقاومت‌های Pull-up یا Pull-down استفاده می‌شود تا پین در حالت پیش‌فرض به یک وضعیت منطقی مشخص (HIGH یا LOW) کشیده شود.

  • Pull-up (Pin.PULL_UP): پین را در حالت پیش‌فرض به HIGH می‌کشد. هنگامی که یک دکمه فشرده می‌شود، پین به LOW کشیده می‌شود.
  • Pull-down (Pin.PULL_DOWN): پین را در حالت پیش‌فرض به LOW می‌کشد. هنگامی که یک دکمه فشرده می‌شود، پین به HIGH کشیده می‌شود.

from machine import Pin
import time

# پین 0 را به عنوان ورودی با مقاومت Pull-up تعریف می کنیم. (مثال برای ESP32)
# این برای دکمه ای که به GND متصل است، مناسب است.
button_pin = Pin(0, Pin.IN, Pin.PULL_UP)

while True:
    if button_pin.value() == 0:  # اگر دکمه فشرده شده باشد (پین به LOW کشیده شده)
        print("دکمه فشرده شد!")
    else:
        print("دکمه رها شده است.")
    time.sleep(0.1) # برای جلوگیری از خواندن مکرر بیش از حد سریع

در کد بالا:

  • Pin(0, Pin.IN, Pin.PULL_UP) یک شیء Pin برای پین GPIO شماره ۰ ایجاد می‌کند و آن را به عنوان ورودی (Pin.IN) با مقاومت Pull-up داخلی (Pin.PULL_UP) تنظیم می‌کند.
  • متد value() وضعیت فعلی پین را می‌خواند (۰ یا ۱).

مفهوم Debouncing در ورودی‌های دیجیتال

هنگامی که یک دکمه مکانیکی فشرده می‌شود یا رها می‌گردد، به دلیل جهش‌های فیزیکی کنتاکت‌ها، ممکن است سیگنال الکتریکی به جای یک تغییر تمیز از LOW به HIGH (یا بالعکس)، برای مدت کوتاهی بین این دو حالت نوسان کند. این پدیده به عنوان “Debouncing” شناخته می‌شود. اگر این نوسانات نادیده گرفته شوند، میکروکنترلر ممکن است چندین بار فشرده شدن دکمه را تشخیص دهد در حالی که کاربر فقط یک بار آن را فشار داده است.

برای مدیریت Debouncing، می‌توان از روش‌های نرم‌افزاری یا سخت‌افزاری استفاده کرد. روش نرم‌افزاری رایج شامل افزودن تأخیر (delay) پس از تشخیص اولیه یک تغییر وضعیت، و سپس خواندن مجدد پین برای تأیید وضعیت پایدار است.


from machine import Pin
import time

# پین 0 به عنوان ورودی با Pull-up
button_pin = Pin(0, Pin.IN, Pin.PULL_UP)
last_button_state = 1 # وضعیت اولیه دکمه (رها شده)
debounce_delay_ms = 50 # میلی ثانیه

while True:
    current_button_state = button_pin.value()

    if current_button_state != last_button_state: # تغییر در وضعیت تشخیص داده شد
        # صبر برای Debounce
        time.sleep_ms(debounce_delay_ms)
        # وضعیت را دوباره بخوان
        current_button_state = button_pin.value()
        
        if current_button_state == 0: # اگر دکمه فشرده شده باشد (بعد از debounce)
            print("دکمه فشرده شد!")
        elif current_button_state == 1: # اگر دکمه رها شده باشد (بعد از debounce)
            print("دکمه رها شد!")
        
        last_button_state = current_button_state # وضعیت جدید را ذخیره کن
    
    time.sleep_ms(10) # برای جلوگیری از مصرف بیش از حد CPU با حلقه خالی

در این مثال Debouncing نرم‌افزاری:

  • پس از تشخیص تغییر در وضعیت دکمه، یک تأخیر کوتاه (۵۰ میلی‌ثانیه) اعمال می‌شود.
  • سپس، وضعیت پین مجدداً بررسی می‌شود تا اطمینان حاصل شود که تغییر وضعیت پایدار است.
  • این روش به طور مؤثری از تشخیص‌های کاذب ناشی از جهش‌های مکانیکی جلوگیری می‌کند.

درک و پیاده‌سازی صحیح ورودی و خروجی دیجیتال، به همراه مدیریت Debouncing، از اولین گام‌های ضروری در توسعه هر پروژه میکروکنترلری است و پایه و اساس تعامل پیچیده‌تر با سخت‌افزار را فراهم می‌آورد.

مدیریت سیگنال‌های آنالوگ: ADC و PWM

دنیای واقعی غالباً آنالوگ است، به این معنی که مقادیر متغیر و پیوسته‌ای از اطلاعات را ارائه می‌دهد (مانند دما، نور، فشار). در مقابل، میکروکنترلرها به طور ذاتی دیجیتال هستند و فقط می‌توانند با مقادیر گسسته (۰ و ۱) کار کنند. برای پل زدن این شکاف، از مبدل‌های آنالوگ به دیجیتال (ADC) و مدولاسیون عرض پالس (PWM) استفاده می‌شود. ADC به میکروکنترلر اجازه می‌دهد تا سیگنال‌های آنالوگ را بخواند، در حالی که PWM به آن امکان می‌دهد تا سیگنال‌های آنالوگ شبیه‌سازی شده را تولید کند.

ADC (Analog-to-Digital Converter): خواندن سنسورهای آنالوگ

یک ADC وظیفه تبدیل یک ولتاژ آنالوگ متغیر به یک مقدار دیجیتال معادل را بر عهده دارد که میکروکنترلر می‌تواند آن را پردازش کند. این فرآیند برای خواندن سنسورهای آنالوگ مانند سنسورهای دما (مثلاً LM35)، فتوسل‌ها (LDR) برای تشخیص نور، پتانسیومترها، و بسیاری دیگر ضروری است.

نحوه کار ADC در میکروپایتون:

در میکروپایتون، ماژول machine کلاس ADC را برای تعامل با مبدل‌های آنالوگ به دیجیتال فراهم می‌کند. پلتفرم‌های مختلف میکروکنترلر (ESP32، ESP8266، RP2040) دارای ADC با ویژگی‌ها و محدودیت‌های خاص خود هستند:

  • ESP32: دارای دو واحد ADC با چندین کانال است. معمولاً رزولوشن ۱۲ بیتی دارد، به این معنی که می‌تواند ولتاژ را به ۴۰۹۶ سطح (۰ تا ۴۰۹۵) تقسیم کند. محدوده ولتاژ ورودی آن معمولاً 0 تا 3.3V است، اما باید به دقت به پین‌های قابل استفاده و کالیبراسیون توجه کرد.
  • ESP8266: تنها یک کانال ADC دارد که معمولاً ۱۰ بیتی است (۱۰۲۴ سطح). محدوده ولتاژ آن نیز محدودتر است (معمولاً 0 تا 1V) و نیاز به مدارهای تقسیم ولتاژ برای سنسورهایی با خروجی ولتاژ بالاتر دارد.
  • Raspberry Pi Pico (RP2040): دارای چهار کانال ADC با رزولوشن ۱۲ بیتی است. محدوده ولتاژ ورودی آن 0 تا 3.3V است و عملکرد پایداری دارد.

مثال: خواندن پتانسیومتر با ADC


from machine import Pin, ADC
import time

# پین ADC را تعریف کنید (مثال برای ESP32، معمولاً پین های ADC مشخصی وجود دارند)
# برای ESP32، ADC1_CHANNEL_0 معمولا به GPIO36 متصل است.
# برای Raspberry Pi Pico، پین های ADC از 26 تا 28 هستند.
# در اینجا از پین 34 برای ESP32 استفاده می شود که به عنوان ADC0_CHANNEL_6 شناخته می شود.
adc_pin = Pin(34) 
adc = ADC(adc_pin)

# تنظیم رزولوشن (فقط برای برخی پلتفرم ها مانند ESP32)
# adc.width(ADC.WIDTH_12BIT) # 0-4095
# تنظیم تضعیف کننده (فقط برای ESP32، برای تنظیم محدوده ولتاژ)
# adc.atten(ADC.ATTN_11DB) # 0-3.3V

while True:
    raw_value = adc.read() # مقدار خام ADC را بخوان
    
    # برای تبدیل به ولتاژ (با فرض 3.3V مرجع و 12 بیت رزولوشن)
    voltage = (raw_value / 4095) * 3.3
    
    print("مقدار خام ADC:", raw_value, "ولتاژ:", round(voltage, 2), "V")
    time.sleep(0.5)

در این مثال:

  • ADC(Pin(34)) یک شیء ADC ایجاد می‌کند که به پین GPIO 34 متصل است.
  • adc.read() یک مقدار عددی (از ۰ تا ۴۰۹۵ برای ۱۲ بیت) را برمی‌گرداند که متناسب با ولتاژ ورودی است.
  • تبدیل مقدار خام به ولتاژ با استفاده از حداکثر مقدار ADC و ولتاژ مرجع انجام می‌شود.

ملاحظات ADC:

  • کالیبراسیون: ADCها ممکن است نیاز به کالیبراسیون داشته باشند تا دقیق‌ترین مقادیر را ارائه دهند.
  • تضعیف (Attenuation): در ESP32، می‌توانید تضعیف‌کننده ADC را برای تنظیم محدوده ولتاژ ورودی (مثلاً 0-1.1V, 0-1.5V, 0-2.2V, 0-3.3V) پیکربندی کنید.
  • نویز: سیگنال‌های آنالوگ می‌توانند به نویز حساس باشند. فیلتر کردن نرم‌افزاری (مانند میانگین‌گیری از چندین نمونه) می‌تواند به بهبود دقت کمک کند.

PWM (Pulse Width Modulation): کنترل عملگرها و شبیه‌سازی آنالوگ

PWM یک تکنیک برای شبیه‌سازی سیگنال‌های آنالوگ با استفاده از یک خروجی دیجیتال است. با تغییر عرض پالس‌های مربعی در یک فرکانس ثابت، می‌توان میانگین ولتاژ خروجی را کنترل کرد. این روش برای کنترل سرعت موتورهای DC، تنظیم روشنایی LEDها، کنترل سروو موتورها و تولید سیگنال‌های صوتی استفاده می‌شود.

نحوه کار PWM در میکروپایتون:

ماژول machine کلاس PWM را برای تولید سیگنال‌های PWM فراهم می‌کند. هنگام تعریف یک شیء PWM، باید پین مورد نظر و فرکانس سیگنال را مشخص کنید. سپس می‌توانید با تنظیم چرخه کاری (Duty Cycle) عرض پالس را کنترل کنید.

  • فرکانس (Frequency): تعداد چرخه‌های پالس در هر ثانیه (هرتز). برای LEDها و موتورها معمولاً چند صد تا چند کیلوهرتز مناسب است. برای سروو موتورها، فرکانس خاصی (معمولاً 50Hz) مورد نیاز است.
  • چرخه کاری (Duty Cycle): نسبت زمانی که پالس در حالت HIGH است به کل دوره پالس. معمولاً به صورت درصدی یا عددی در یک محدوده خاص (مثلاً 0 تا 1023 یا 0 تا 65535) بیان می‌شود.

مثال: کنترل روشنایی LED با PWM


from machine import Pin, PWM
import time

# پین PWM را تعریف می کنیم (مثال برای ESP32، پین 2)
# Raspberry Pi Pico از اکثر پین ها برای PWM پشتیبانی می کند.
pwm_pin = Pin(2) 
pwm = PWM(pwm_pin)

# فرکانس PWM را تنظیم می کنیم (مثلاً 1000 هرتز)
pwm.freq(1000)

# حلقه برای تغییر روشنایی LED
for duty_cycle in range(0, 1024, 4): # از 0 تا 1023 برای ESP32 (10 بیت)
    pwm.duty(duty_cycle) # تنظیم چرخه کاری (0 تا 1023)
    time.sleep_ms(10)

for duty_cycle in range(1023, -1, -4): # از 1023 تا 0
    pwm.duty(duty_cycle)
    time.sleep_ms(10)

# برای ESP32 و RP2040، می توان از duty_u16 برای 16 بیت (0-65535) استفاده کرد
# for duty_cycle in range(0, 65536, 256):
#     pwm.duty_u16(duty_cycle)
#     time.sleep_ms(10)

در این مثال:

  • PWM(Pin(2)) یک شیء PWM روی پین GPIO 2 ایجاد می‌کند.
  • pwm.freq(1000) فرکانس PWM را به ۱۰۰۰ هرتز تنظیم می‌کند.
  • pwm.duty(duty_cycle) چرخه کاری را تنظیم می‌کند. برای ESP32، متد duty() معمولاً از ۰ تا ۱۰۲۳ را می‌پذیرد (۱۰ بیت). برای RP2040، متد duty_u16() برای مقادیر ۰ تا ۶۵۵۳۵ (۱۶ بیت) وجود دارد.

مثال: کنترل سروو موتور با PWM

سروو موتورها به سیگنال PWM با فرکانس خاص (معمولاً 50Hz) و چرخه کاری در یک محدوده کوچک برای تعیین موقعیت نیاز دارند.


from machine import Pin, PWM
import time

# پین PWM برای سروو موتور (مثلاً پین 4 در ESP32)
servo_pin = Pin(4)
servo_pwm = PWM(servo_pin, freq=50) # فرکانس 50 هرتز برای سروو

# توابع برای تنظیم زاویه سروو
def set_servo_angle(angle):
    # فرض کنید برای سرووی SG90:
    # 0 درجه -> duty_u16 ~ 1000 (0.5ms pulse)
    # 90 درجه -> duty_u16 ~ 4000 (1.5ms pulse)
    # 180 درجه -> duty_u16 ~ 7000 (2.5ms pulse)
    # این مقادیر تقریبی هستند و ممکن است نیاز به کالیبراسیون داشته باشند.
    
    # فرمول خطی برای تبدیل زاویه به duty_u16
    # duty = map(angle, 0, 180, 1000, 7000)
    min_duty = 1000 # مثلاً برای 0 درجه
    max_duty = 7000 # مثلاً برای 180 درجه
    duty = int(min_duty + (angle / 180) * (max_duty - min_duty))
    servo_pwm.duty_u16(duty)
    print(f"تنظیم سروو روی {angle} درجه، Duty: {duty}")

# حرکت سروو به موقعیت های مختلف
set_servo_angle(0)
time.sleep(1)
set_servo_angle(90)
time.sleep(1)
set_servo_angle(180)
time.sleep(1)
set_servo_angle(45)
time.sleep(1)

# قطع PWM برای صرفه جویی در مصرف انرژی
# servo_pwm.deinit() 

در این بخش، ما یاد گرفتیم چگونه با استفاده از ADC سیگنال‌های آنالوگ را از سنسورها بخوانیم و با PWM سیگنال‌های آنالوگ شبیه‌سازی شده را برای کنترل عملگرها تولید کنیم. این دو قابلیت، ابزارهای قدرتمندی برای تعامل میکروکنترلر با دنیای فیزیکی هستند.

پروتکل‌های ارتباطی پیشرفته: I2C، SPI و UART

برای تعامل با سنسورهای پیچیده‌تر، نمایشگرها، و سایر دستگاه‌های جانبی، پین‌های GPIO به تنهایی کافی نیستند. میکروکنترلرها از پروتکل‌های ارتباطی استاندارد صنعتی استفاده می‌کنند که امکان تبادل داده‌های بیشتر و با سرعت بالاتر را فراهم می‌کنند. سه پروتکل رایج و مهم در میکروپایتون، I2C، SPI و UART هستند.

I2C (Inter-Integrated Circuit)

I2C یک پروتکل ارتباطی سریال همزمان (synchronous serial) است که توسط Philips (اکنون NXP) توسعه یافته است. این پروتکل برای ارتباط بین یک میکروکنترلر (به عنوان Master) و چندین دستگاه جانبی (به عنوان Slave) طراحی شده است. I2C از تنها دو خط سیگنال برای انتقال داده استفاده می‌کند:

  • SDA (Serial Data Line): خط داده
  • SCL (Serial Clock Line): خط ساعت

ویژگی‌های کلیدی I2C:

  • Master/Slave: یک یا چند Master می‌توانند با چندین Slave ارتباط برقرار کنند. هر Slave دارای یک آدرس ۷ بیتی یا ۱۰ بیتی منحصر به فرد است.
  • Multimaster: امکان وجود چندین Master در یک باس وجود دارد.
  • Low Pin Count: فقط دو پین نیاز دارد.
  • PULL_UP Resistors: خطوط SDA و SCL نیاز به مقاومت‌های Pull-up دارند (معمولاً 4.7kΩ). برخی ماژول‌ها این مقاومت‌ها را داخلی دارند.
  • Broadcast: Master می‌تواند به همه Slaveها به طور همزمان داده ارسال کند.

استفاده از I2C در میکروپایتون:

ماژول machine کلاس I2C را برای کار با این پروتکل ارائه می‌دهد. برای استفاده از آن، باید پین‌های SDA و SCL و فرکانس باس را مشخص کنید.

مثال: اتصال به سنسور BME280 (دما، رطوبت، فشار) با I2C


from machine import Pin, I2C
import time
import bme280 # فرض می کنیم کتابخانه bme280.py در برد آپلود شده است

# تعریف پین های I2C (مثال برای ESP32)
# برای ESP32 معمولاً: SDA=Pin(21), SCL=Pin(22)
# برای Raspberry Pi Pico معمولاً: SDA=Pin(4), SCL=Pin(5)
i2c = I2C(1, scl=Pin(22), sda=Pin(21), freq=400000) # Bus 1, 400kHz

# اسکن آدرس های I2C برای یافتن دستگاه ها
print("I2C devices found:", i2c.scan())

# آدرس BME280 معمولاً 0x76 یا 0x77 است.
bme = bme280.BME280(i2c=i2c, address=0x76) 

while True:
    # خواندن داده ها از سنسور
    # bme.values() متدی است که دما، فشار و رطوبت را به صورت تاپل برمی گرداند.
    temperature, pressure, humidity = bme.values() 
    
    print(f"دما: {temperature}، فشار: {pressure}، رطوبت: {humidity}")
    time.sleep(2)

در این مثال، ابتدا یک شیء I2C با پین‌های مشخص و فرکانس ۴۰۰kHz ایجاد می‌کنیم. سپس با i2c.scan() دستگاه‌های متصل را پیدا می‌کنیم. در نهایت، با استفاده از یک درایور bme280 (که باید جداگانه روی برد آپلود شود)، داده‌ها را از سنسور می‌خوانیم.

SPI (Serial Peripheral Interface)

SPI یک پروتکل ارتباطی سریال همزمان دیگر است که به دلیل سادگی و سرعت بالا شناخته شده است. برخلاف I2C که فقط دو خط دارد، SPI از چهار خط سیگنال اصلی استفاده می‌کند و برای کاربردهایی که نیاز به نرخ انتقال داده بالاتر دارند (مانند نمایشگرهای LCD، ماژول‌های SD کارت، سنسورهای با وضوح بالا) مناسب است:

  • MOSI (Master Out Slave In): خط داده از Master به Slave
  • MISO (Master In Slave Out): خط داده از Slave به Master
  • SCK (Serial Clock): خط ساعت تولید شده توسط Master
  • CS/SS (Chip Select/Slave Select): خط انتخاب Slave، توسط Master برای فعال کردن Slave مورد نظر استفاده می‌شود.

ویژگی‌های کلیدی SPI:

  • Full-Duplex: Master و Slave می‌توانند به طور همزمان داده ارسال و دریافت کنند.
  • High Speed: می‌تواند با فرکانس‌های بسیار بالا (تا ده‌ها مگاهرتز) کار کند.
  • Single Master: معمولاً یک Master وجود دارد.
  • More Pins: به پین‌های بیشتری نسبت به I2C نیاز دارد.
  • No Addressing: هر Slave با یک خط CS/SS جداگانه انتخاب می‌شود.

استفاده از SPI در میکروپایتون:

کلاس SPI در ماژول machine این امکان را فراهم می‌کند. برای تعریف یک شیء SPI، باید پین‌های MOSI، MISO، SCK و فرکانس را مشخص کنید. پین CS/SS معمولاً به عنوان یک GPIO معمولی برای کنترل فعال/غیرفعال کردن دستگاه Slave مدیریت می‌شود.

مثال: ارتباط با نمایشگر OLED SSD1306 (با فرض اینکه از SPI استفاده می‌کند)


from machine import Pin, SPI
import ssd1306 # فرض می کنیم کتابخانه ssd1306.py برای نمایشگر OLED در برد آپلود شده است
import framebuf # معمولاً همراه با ssd1306 استفاده می شود

# تعریف پین های SPI (مثال برای ESP32)
# برای ESP32: MOSI=Pin(23), MISO=Pin(19), SCK=Pin(18)
# برای Raspberry Pi Pico: MOSI=Pin(19), MISO=Pin(16), SCK=Pin(18)
spi = SPI(1, baudrate=10000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19))

# پین های CS و DC و RESET برای SSD1306
cs = Pin(5)
dc = Pin(17)
res = Pin(16)

# ایجاد شیء OLED
oled = ssd1306.SSD1306_SPI(128, 64, spi, dc, res, cs)

# پاک کردن نمایشگر
oled.fill(0)
oled.show()

# نوشتن متن و نمایش آن
oled.text("Hello, MicroPython!", 0, 0)
oled.text("SPI OLED Demo", 0, 10)
oled.pixel(64, 32, 1) # رسم یک پیکسل
oled.show()

time.sleep(5)
oled.fill(0)
oled.show()

در این مثال، SPI با سرعت ۱۰MHz پیکربندی می‌شود. سپس، با استفاده از درایور ssd1306 و مدیریت پین‌های CS، DC و RESET، می‌توانیم روی نمایشگر OLED متن و گرافیک نمایش دهیم.

UART (Universal Asynchronous Receiver/Transmitter)

UART یک پروتکل ارتباطی سریال ناهمزمان است که به دلیل سادگی و تطبیق‌پذیری‌اش بسیار مورد استفاده قرار می‌گیرد. ناهمزمان بودن به این معنی است که نیازی به خط ساعت مشترک بین فرستنده و گیرنده نیست. در عوض، از تنظیمات مشترک مانند نرخ انتقال (Baud Rate) و فرمت فریم برای همگام‌سازی استفاده می‌شود. UART برای ارتباط با ماژول‌های GPS، ماژول‌های بلوتوث سریال، سایر میکروکنترلرها، یا کنسول سریال کامپیوتر استفاده می‌شود.

UART از دو خط سیگنال اصلی استفاده می‌کند:

  • TX (Transmit): برای ارسال داده
  • RX (Receive): برای دریافت داده

ویژگی‌های کلیدی UART:

  • Asynchronous: بدون نیاز به خط ساعت جداگانه.
  • Full-Duplex: ارسال و دریافت همزمان داده.
  • Simple: تنها دو پین نیاز دارد.
  • Configurable: نرخ انتقال، بیت‌های داده، بیت‌های توقف، توازن (Parity) قابل تنظیم هستند.

استفاده از UART در میکروپایتون:

کلاس UART در ماژول machine امکان تعامل با این پروتکل را فراهم می‌کند. باید شماره باس UART (مثلاً 0 یا 1)، پین‌های TX و RX و نرخ انتقال (Baud Rate) را مشخص کنید.

مثال: ارتباط با یک ماژول GPS سریال


from machine import Pin, UART
import time

# تعریف UART (مثال برای ESP32، UART2)
# برای ESP32: UART(1, tx=Pin(17), rx=Pin(16))
# برای Raspberry Pi Pico: UART(0, tx=Pin(0), rx=Pin(1))
# توجه: UART0 معمولاً برای کنسول REPL استفاده می شود، پس از UART1/UART2 استفاده کنید.
uart = UART(2, baudrate=9600, tx=Pin(17), rx=Pin(16)) # UART2 on ESP32

print("Initializing UART for GPS module...")

while True:
    if uart.any(): # اگر داده ای برای خواندن وجود دارد
        data = uart.readline() # یک خط کامل را بخوان
        try:
            # داده های GPS معمولاً به فرمت NMEA هستند.
            # می توانید این داده ها را پردازش کنید.
            print("Received:", data.decode('utf-8').strip())
            # مثال: اگر خط شامل "$GPGGA" باشد، آن را پردازش کنید
            if b'$GPGGA' in data:
                # منطق پردازش داده های GPGGA
                pass
        except UnicodeError:
            print("Received non-UTF-8 data.")
    time.sleep(1)

در این مثال، UART(2, ...) باس UART شماره ۲ را با پین‌های مشخص و نرخ انتقال 9600 baud پیکربندی می‌کند. uart.any() بررسی می‌کند که آیا داده‌ای در بافر ورودی UART وجود دارد یا خیر، و uart.readline() یک خط کامل از داده‌ها را می‌خواند.

پروتکل‌های I2C، SPI و UART ابزارهای ضروری برای هر توسعه‌دهنده سیستم‌های امبدد هستند که به آن‌ها اجازه می‌دهند با طیف وسیعی از دستگاه‌ها ارتباط برقرار کرده و عملکردهای پیچیده‌ای را پیاده‌سازی کنند.

مدیریت وقفه‌ها (Interrupts) و رویدادها

در سیستم‌های امبدد، پاسخگویی بلادرنگ به رویدادهای خارجی اغلب حیاتی است. تصور کنید در حال نظارت بر یک سنسور حرکتی هستید؛ نمی‌توانید به طور مداوم پین آن را در یک حلقه while True بررسی کنید (Polling)، زیرا این کار منابع CPU را هدر می‌دهد و ممکن است رویدادهای سریع را از دست بدهد. اینجا است که وقفه‌ها (Interrupts) وارد عمل می‌شوند.

وقفه‌ها چیست و چرا حیاتی هستند؟

وقفه یک مکانیزم سخت‌افزاری است که به میکروکنترلر اجازه می‌دهد تا به صورت خودکار اجرای برنامه اصلی خود را متوقف کرده، یک کار خاص را در پاسخ به یک رویداد خارجی (مانند فشرده شدن یک دکمه، دریافت داده روی UART، پایان یک تبدیل ADC) انجام دهد، و سپس به اجرای برنامه اصلی بازگردد. این رویکرد به میکروکنترلر اجازه می‌دهد تا به طور کارآمدتری از منابع خود استفاده کند و به رویدادها فوراً واکنش نشان دهد.

مزایای استفاده از وقفه‌ها:

  • پاسخگویی بلادرنگ (Real-time responsiveness): میکروکنترلر می‌تواند فوراً به رویدادها پاسخ دهد.
  • کارایی بالاتر CPU: به جای polling مداوم، CPU آزاد است تا کارهای دیگر را انجام دهد.
  • کد تمیزتر: منطق مدیریت رویداد از حلقه اصلی برنامه جدا می‌شود.
  • صرفه‌جویی در انرژی: میکروکنترلر می‌تواند در حالت کم مصرف (مانند Deep Sleep) باشد و تنها با یک وقفه بیدار شود.

استفاده از وقفه‌های GPIO در میکروپایتون: متد Pin.irq()

میکروپایتون یک رابط قدرتمند برای مدیریت وقفه‌های GPIO از طریق متد Pin.irq() ارائه می‌دهد. این متد به شما امکان می‌دهد تا یک تابع (به نام ISR – Interrupt Service Routine یا Callback Function) را برای اجرا در هنگام وقوع یک رویداد خاص روی یک پین مشخص ثبت کنید.

پارامترهای اصلی Pin.irq():

  • handler: تابعی است که هنگام وقوع وقفه فراخوانی می‌شود. این تابع باید یک پارامتر (شیء Pin که وقفه را ایجاد کرده) را بپذیرد.
  • trigger: مشخص می‌کند که در چه رویدادی وقفه فعال شود:
    • Pin.IRQ_FALLING: وقفه در هنگام تغییر پین از HIGH به LOW (لبه نزولی)
    • Pin.IRQ_RISING: وقفه در هنگام تغییر پین از LOW به HIGH (لبه صعودی)
    • Pin.IRQ_BOTH: وقفه در هر دو لبه صعودی و نزولی
  • hard (اختیاری): اگر True باشد، ISR به عنوان یک وقفه سخت‌افزاری (Hard Interrupt) ثبت می‌شود که سریع‌تر و با اولویت بالاتر اجرا می‌شود اما دارای محدودیت‌هایی (مانند عدم تخصیص حافظه و استفاده از توابع blocking) است. برای بیشتر موارد، حالت پیش‌فرض (hard=False) که یک وقفه نرم‌افزاری (Soft Interrupt) است، کافی و امن‌تر است.

مثال: تشخیص فشرده شدن دکمه با وقفه


from machine import Pin
import time

# یک متغیر سراسری برای شمارش وقفه ها
interrupt_count = 0
last_interrupt_time = 0 # برای Debouncing نرم افزاری

# تابع ISR (Interrupt Service Routine)
def button_pressed_handler(pin):
    global interrupt_count, last_interrupt_time
    current_time = time.ticks_ms()
    
    # Debouncing نرم افزاری: حداقل 200 میلی ثانیه بین وقفه ها فاصله باشد.
    if (current_time - last_interrupt_time) > 200:
        interrupt_count += 1
        print(f"وقفه روی پین {pin.id()}! تعداد کل وقفه ها: {interrupt_count}")
        last_interrupt_time = current_time

# تعریف پین دکمه (مثال برای ESP32، پین 0 با Pull-up داخلی)
# فرض کنید دکمه به GND متصل است، پس لبه نزولی (HIGH به LOW) را تشخیص می دهیم.
button_pin = Pin(0, Pin.IN, Pin.PULL_UP)

# ثبت وقفه برای پین دکمه در لبه نزولی
button_pin.irq(trigger=Pin.IRQ_FALLING, handler=button_pressed_handler)

print("برنامه اصلی در حال اجرا است. دکمه را فشار دهید.")

# حلقه اصلی برنامه می تواند کارهای دیگر را انجام دهد
while True:
    # اینجا می توانیم کارهای دیگر را انجام دهیم
    time.sleep(1) # فقط برای اینکه برنامه بی نهایت CPU را اشغال نکند
    # print("Heartbeat...")

در این مثال:

  • تابع button_pressed_handler به عنوان ISR عمل می‌کند. این تابع هنگام فشرده شدن دکمه (لبه نزولی) فراخوانی می‌شود.
  • درون ISR، یک منطق Debouncing نرم‌افزاری ساده با استفاده از time.ticks_ms() و بررسی زمان آخرین وقفه پیاده‌سازی شده است تا از تشخیص‌های کاذب جلوگیری شود.
  • حلقه while True اصلی برنامه می‌تواند بدون نگرانی از از دست دادن رویداد دکمه، به انجام کارهای دیگر بپردازد.

ملاحظات مهم در نوشتن ISRs

ISRs در یک زمینه خاص (Interrupt Context) اجرا می‌شوند و دارای محدودیت‌هایی هستند که رعایت آن‌ها برای پایداری سیستم بسیار مهم است:

  1. کوتاه و سریع: ISRها باید تا حد امکان کوتاه و سریع باشند. هرگونه عملیات زمان‌بر (مانند time.sleep()، چاپ به کنسول، یا عملیات پیچیده I/O) باید از ISR پرهیز شود.
  2. پرهیز از تخصیص حافظه (Memory Allocation): از ایجاد اشیاء جدید یا استفاده از توابعی که ممکن است حافظه پویا تخصیص دهند (مانند لیست‌ها، دیکشنری‌ها، یا برخی متدهای رشته‌ای) در ISR خودداری کنید. این کار می‌تواند منجر به کرش شدن سیستم شود.
  3. استفاده از متغیرهای سراسری (Global Variables): برای ارتباط بین ISR و حلقه اصلی، از متغیرهای سراسری استفاده کنید. اطمینان حاصل کنید که این متغیرها به درستی تعریف شده‌اند (با کلمه کلیدی global در Python).
  4. deferring کارها: اگر کاری پیچیده‌تر از یک عملیات ساده باید در پاسخ به یک وقفه انجام شود، بهتر است فقط یک پرچم (flag) را در ISR تنظیم کنید و سپس حلقه اصلی برنامه آن پرچم را بررسی کرده و کار پیچیده‌تر را در خارج از ISR انجام دهد.

مثال: deferring کار از ISR به حلقه اصلی


from machine import Pin
import time

# متغیر سراسری برای سیگنال دهی از ISR به حلقه اصلی
button_flag = False

def button_handler(pin):
    global button_flag
    # فقط یک پرچم را تنظیم کن، کار پیچیده را به حلقه اصلی واگذار کن
    button_flag = True

button_pin = Pin(0, Pin.IN, Pin.PULL_UP)
button_pin.irq(trigger=Pin.IRQ_FALLING, handler=button_handler)

print("برنامه اصلی در حال اجرا است. دکمه را فشار دهید.")

while True:
    if button_flag:
        # کار پیچیده تر را در اینجا انجام بده
        print("دکمه فشرده شد! (پردازش در حلقه اصلی)")
        # debounce نرم افزاری را می توان اینجا یا در ISR انجام داد
        time.sleep_ms(200) # برای جلوگیری از تشخیص های مکرر
        button_flag = False # پرچم را ریست کن
    
    time.sleep_ms(50) # برای جلوگیری از مصرف بیش از حد CPU با حلقه خالی

در این رویکرد، ISR فقط پرچم button_flag را تنظیم می‌کند. حلقه اصلی به طور مداوم این پرچم را بررسی کرده و در صورت True بودن، عملیات مورد نظر را انجام می‌دهد و سپس پرچم را ریست می‌کند. این روش بهترین عملکرد و پایداری را برای سیستم‌های مبتنی بر وقفه فراهم می‌آورد.

مدیریت صحیح وقفه‌ها یکی از مهارت‌های کلیدی برای توسعه سیستم‌های امبدد قابل اعتماد و با کارایی بالا در میکروپایتون است.

ملاحظات پیشرفته و بهینه‌سازی در مدیریت GPIO

فراتر از مفاهیم اساسی، بهینه‌سازی و در نظر گرفتن جوانب پیشرفته‌تر در مدیریت GPIO می‌تواند تفاوت قابل توجهی در عملکرد، پایداری، مصرف انرژی و حتی هزینه نهایی یک پروژه ایجاد کند. این بخش به بررسی این ملاحظات حیاتی می‌پردازد.

صرفه‌جویی در مصرف انرژی: حالت‌های Low-Power

برای دستگاه‌های باتری‌خور یا کاربردهایی که نیاز به استقرار طولانی‌مدت دارند، مصرف انرژی یک عامل بسیار مهم است. میکروکنترلرها، از جمله خانواده ESP و RP2040، حالت‌های مختلفی از کم‌مصرفی را ارائه می‌دهند که با میکروپایتون قابل دسترسی هستند.

  • Light Sleep: در این حالت، CPU متوقف می‌شود اما RTC (Real-Time Clock) و برخی از پریفرال‌ها فعال می‌مانند. بیدار شدن از این حالت سریع‌تر است و مصرف انرژی کمتری نسبت به حالت فعال دارد. GPIOها می‌توانند به عنوان منبع بیداری پیکربندی شوند.
  • Deep Sleep: در این حالت، تقریباً تمام اجزای میکروکنترلر خاموش می‌شوند به جز بخش RTC و حداقل حافظه مورد نیاز برای حفظ وضعیت. این حالت کمترین مصرف انرژی را دارد اما زمان بیداری آن طولانی‌تر است. بیداری از Deep Sleep می‌تواند توسط تایمر RTC، یا تغییر وضعیت یک پین GPIO (RTC_GPIO) انجام شود.

استفاده از حالت Deep Sleep برای بیداری با GPIO (مثال برای ESP32):


from machine import Pin, deepsleep
import esp32 # برای توابع مربوط به deepsleep در ESP32
import time

# پین GPIO را که می خواهیم از آن به عنوان منبع بیداری استفاده کنیم، تعریف کنید.
# این پین ها معمولاً پین های خاصی هستند که می توانند به عنوان RTC_GPIO عمل کنند.
# برای ESP32، پین 32 تا 39 معمولاً RTC_GPIO هستند.
wake_up_pin = Pin(33, Pin.IN, Pin.PULL_UP) 

def run_on_wakeup():
    print("برد از خواب عمیق بیدار شد!")
    # کارهایی که باید پس از بیداری انجام شود.
    
# تعیین منبع بیداری: فعال سازی با تغییر وضعیت پین (مثلاً از HIGH به LOW)
# esp32.wake_on_ext0(pin=wake_up_pin, level=esp32.WAKEUP_ALL_LOW) 
# یا با لبه (rising/falling) در ESP32
# esp32.wake_on_ext1(pins=[wake_up_pin], level=esp32.WAKEUP_ALL_LOW) 
# برای ESP32، wake_on_ext0 یک پین خاص و wake_on_ext1 تا 8 پین را پشتیبانی می کند.

# برای مثال از wake_on_ext1 استفاده میکنیم
esp32.wake_on_ext1(pins=(wake_up_pin,), level=esp32.WAKEUP_ANY_HIGH) # بیداری با هر HIGH

# ابتدا یک بار دکمه را برای تست فشار دهید.
# سپس برد را به Deep Sleep می فرستیم.
print("Going to deep sleep. Press the button to wake up.")
time.sleep(1) # تا پیام چاپ شود
deepsleep() # برد را به Deep Sleep می فرستد

توجه داشته باشید که پس از deepsleep()، برد ریست می‌شود و اجرای برنامه از ابتدا آغاز می‌شود. برای تشخیص منبع بیداری (مثلاً از دکمه بود یا تایمر)، می‌توانید از esp32.wake_reason() استفاده کنید.

مدیریت خطا و پایداری: Watchdog Timers و Exception Handling

سیستم‌های امبدد اغلب بدون نظارت انسانی برای مدت‌های طولانی کار می‌کنند و باید در برابر خطاها مقاوم باشند. دو ابزار مهم برای افزایش پایداری:

  • Watchdog Timer (WDT): یک تایمر سخت‌افزاری است که در صورت عدم “بازنشانی” (feeding) آن توسط برنامه اصلی در یک بازه زمانی مشخص، میکروکنترلر را ریست می‌کند. این کار از قفل شدن برنامه (deadlock) جلوگیری کرده و تضمین می‌کند که سیستم همیشه در حال کار است.
    
    from machine import WDT
    import time
    
    # WDT را با یک timeout 5 ثانیه ای فعال می کنیم
    # اگر در 5 ثانیه WDT.feed() فراخوانی نشود، برد ریست می شود.
    wdt = WDT(timeout=5000) 
    
    print("WDT فعال شد. هر 1 ثانیه WDT را feed می کنیم.")
    
    try:
        while True:
            wdt.feed() # WDT را بازنشانی می کند
            print("WDT fed.")
            time.sleep(1)
            
            # برای تست: می توانید خط زیر را uncomment کنید تا WDT ریست شود.
            # if time.ticks_ms() > 10000: # پس از 10 ثانیه، WDT دیگر feed نمی شود
            #    while True:
            #        pass # برنامه قفل می شود و WDT باید ریست کند
    except KeyboardInterrupt:
        print("برنامه متوقف شد.")
    
  • Exception Handling (try-except): استفاده از بلاک‌های try-except پایتون برای مدیریت خطاهای پیش‌بینی شده در طول اجرای برنامه، به ویژه در عملیات I/O که ممکن است با شکست مواجه شوند (مانند ارتباط I2C با یک دستگاه قطع شده).

مسائل مربوط به ایمپدانس و تداخل: Pull-up/down خارجی

در حالی که بسیاری از میکروکنترلرها دارای مقاومت‌های Pull-up/down داخلی هستند، در برخی موارد استفاده از مقاومت‌های خارجی ترجیح داده می‌شود:

  • کنترل دقیق‌تر: مقاومت‌های داخلی معمولاً دارای مقدار ثابتی هستند، در حالی که مقاومت‌های خارجی امکان انتخاب مقادیر دقیق‌تر را برای بهینه‌سازی مصرف انرژی یا مطابقت با نیازهای خاص سنسور فراهم می‌کنند.
  • کاهش نویز: در محیط‌های پر نویز، مقاومت‌های Pull-up/down با مقدار مناسب می‌توانند به پایدارسازی سیگنال کمک کرده و از تغییرات ناخواسته وضعیت پین جلوگیری کنند.
  • بافرینگ: در برخی موارد، برای محافظت از پین‌های میکروکنترلر در برابر جریان‌های بالا یا ولتاژهای نامناسب، یا برای تقویت سیگنال، از بافرها یا ترانزیستورها استفاده می‌شود.

امنیت پین‌ها: حفاظت در برابر اضافه جریان و ESD

پین‌های GPIO مستعد آسیب ناشی از:

  • اضافه جریان (Over-current): اگر یک پین خروجی به یک بار با مقاومت بسیار کم متصل شود، جریان بیش از حد می‌تواند به پین و حتی میکروکنترلر آسیب برساند. همیشه از مقاومت‌های محدودکننده جریان (مخصوصاً با LEDها) استفاده کنید.
  • ولتاژ بیش از حد (Over-voltage): اعمال ولتاژ بالاتر از حداکثر مجاز به یک پین ورودی/خروجی می‌تواند آن را خراب کند. از تقسیم‌کننده‌های ولتاژ (Voltage Dividers) یا مبدل‌های سطح ولتاژ (Level Shifters) استفاده کنید.
  • ESD (Electrostatic Discharge): تخلیه الکترواستاتیکی می‌تواند به مدارهای حساس میکروکنترلر آسیب برساند. رعایت اصول ESD هنگام کار با سخت‌افزار (مانند استفاده از مچ‌بند ESD) توصیه می‌شود.

انتخاب پلتفرم: ESP32 در مقابل RP2040

انتخاب میکروکنترلر مناسب برای پروژه، بر اساس قابلیت‌های GPIO و نیازهای کلی پروژه اهمیت دارد:

  • ESP32:
    • نقاط قوت: Wi-Fi و Bluetooth داخلی، پردازش قوی، تعداد زیادی پین GPIO، چندین ADC/PWM/I2C/SPI/UART. عالی برای پروژه‌های IoT متصل و شبکه‌ای.
    • ملاحظات: برخی پین‌های GPIO (مانند GPIO6-11) برای فلش SPI استفاده می‌شوند و باید با احتیاط استفاده شوند. پین‌های ADC ممکن است نیاز به کالیبراسیون داشته باشند.
  • Raspberry Pi Pico (RP2040):
    • نقاط قوت: پردازنده دو هسته‌ای قدرتمند، SRAM زیاد، GPIOهای انعطاف‌پذیر، PIO (Programmable I/O) برای پروتکل‌های سفارشی، ADC پایدارتر. عالی برای پروژه‌هایی که نیاز به زمان‌بندی دقیق، پردازش محلی، و کنترل سخت‌افزار سطح پایین دارند.
    • ملاحظات: بدون اتصال بی‌سیم داخلی (نیاز به ماژول خارجی).

درک این ملاحظات پیشرفته به مهندسان و برنامه‌نویسان کمک می‌کند تا سیستم‌های امبدد قوی‌تر، پایدارتر، کارآمدتر و قابل اعتمادتر با میکروپایتون طراحی کنند.

نتیجه‌گیری و چشم‌انداز آینده

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

ما با اصول اولیه کار با پین‌های ورودی و خروجی دیجیتال شروع کردیم، یاد گرفتیم که چگونه یک LED را چشمک بزنیم و یک دکمه را بخوانیم، و اهمیت Debouncing را درک کردیم. سپس به دنیای سیگنال‌های آنالوگ قدم گذاشتیم و نحوه استفاده از ADC برای خواندن سنسورها و PWM برای کنترل عملگرها مانند LEDها و سروو موتورها را بررسی کردیم. پروتکل‌های ارتباطی پیشرفته I2C، SPI و UART به ما امکان دادند تا با سنسورهای پیچیده‌تر، نمایشگرها و سایر ماژول‌ها ارتباط برقرار کنیم، که هر یک کاربردها و ویژگی‌های منحصر به فرد خود را دارند.

در ادامه، به سراغ مبحث حیاتی وقفه‌ها (Interrupts) رفتیم و دیدیم که چگونه این مکانیزم سخت‌افزاری، پاسخگویی بلادرنگ و کارایی CPU را به میزان قابل توجهی افزایش می‌دهد، در حالی که بهترین شیوه‌ها برای نوشتن ISRهای پایدار را نیز مورد بحث قرار دادیم. در نهایت، ملاحظات پیشرفته‌ای مانند صرفه‌جویی در مصرف انرژی با حالت‌های Deep Sleep، افزایش پایداری با Watchdog Timers، مدیریت خطا، حفاظت پین‌ها و نکات مهم در انتخاب پلتفرم (ESP32 در مقابل RP2040) را تشریح کردیم.

چشم‌انداز آینده و کاربردها:

توانایی مدیریت جامع پین‌های GPIO با میکروپایتون، درهای بسیاری را به روی کاربردهای بی‌شمار باز می‌کند:

  • اینترنت اشیا (IoT): از سیستم‌های اتوماسیون خانگی هوشمند گرفته تا حسگرهای محیطی صنعتی، میکروپایتون و GPIOها ستون فقرات جمع‌آوری داده و کنترل دستگاه‌ها در شبکه‌های IoT هستند.
  • رباتیک: کنترل موتورها، خواندن سنسورهای مجاورت، و تعامل با ماژول‌های ناوبری، همگی نیازمند مدیریت دقیق GPIO هستند.
  • سیستم‌های مانیتورینگ: ساخت سیستم‌هایی برای پایش دما، رطوبت، کیفیت هوا، یا سایر پارامترهای فیزیکی در کشاورزی هوشمند، محیط‌های صنعتی، یا مراکز داده.
  • پروژه‌های سرگرمی و آموزشی: میکروپایتون یک بستر عالی برای آموزش الکترونیک و برنامه‌نویسی به دانش‌آموزان و علاقه‌مندان است، و کنترل GPIOها بخش اساسی این فرآیند است.
  • نمونه‌سازی سریع (Rapid Prototyping): سرعت توسعه بالا با میکروپایتون، آن را به ابزاری ایده‌آل برای اعتبارسنجی سریع ایده‌ها و مفاهیم در مراحل اولیه طراحی محصول تبدیل می‌کند.

با پیشرفت سخت‌افزار میکروکنترلرها و تکامل مستمر میکروپایتون، انتظار می‌رود که ابزارها و قابلیت‌های مدیریت GPIO همچنان قدرتمندتر و کاربرپسندتر شوند. جامعه فعال میکروپایتون نیز به طور مداوم در حال توسعه کتابخانه‌ها و درایورهای جدید برای سنسورها و عملگرهای متنوع است، که دسترسی به سخت‌افزارهای جدید را آسان‌تر می‌کند.

در نهایت، تسلط بر مدیریت پین‌های GPIO در میکروپایتون نه تنها یک مهارت فنی ارزشمند است، بلکه به شما این امکان را می‌دهد که ایده‌های خود را از دنیای دیجیتال به واقعیت فیزیکی تبدیل کنید و با ایجاد سیستم‌های هوشمند و متصل، به نوآوری در حوزه‌های مختلف کمک کنید. با استفاده از دانش کسب شده در این مقاله، شما اکنون مجهز به ابزارهایی هستید که می‌توانید پروژه‌های امبدد پیچیده و خلاقانه‌ای را با اطمینان و کارایی بالا توسعه دهید.

“تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”

قیمت اصلی 2.290.000 ریال بود.قیمت فعلی 1.590.000 ریال است.

"تسلط به برنامه‌نویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"

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

ویژگی‌های کلیدی:

بدون نیاز به تجربه قبلی برنامه‌نویسی

زیرنویس فارسی با ترجمه حرفه‌ای

۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان