
آموزش استفاده از PIO در Raspberry Pi Pico؛ چگونه بدون درگیرکردن پردازنده سیگنالهای دقیق تولید کنیم؟
یکی از چالشهای رایج در برنامهنویسی میکروکنترلرها، تولید و دریافت سیگنالهای دیجیتال با زمانبندی دقیق است. برای مثال، تصور کنید بخواهید یک پروتکل ارتباطی اختصاصی ایجاد کنید که در آن چندین پایه GPIO باید با فاصله زمانی مشخص تغییر وضعیت دهند.
این واحد سختافزاری به شما اجازه میدهد توالی مشخصی از عملیات ورودی و خروجی را تعریف کنید تا بدون نیاز به اجرای مداوم دستورها توسط CPU، آن عملیات با زمانبندی قابل پیشبینی اجرا شوند. در این مقاله با معماری PIO، نحوه برنامهنویسی آن و یک مثال عملی برای تولید سیگنال دیجیتال آشنا میشویم.
PIO چیست و چرا در Raspberry Pi Pico اهمیت دارد؟
PIO مخفف Programmable Input/Output است. این واحد در تراشه RP2040 تعبیه شده و وظیفه آن فراهمکردن امکان پیادهسازی رابطها و پروتکلهای ورودی و خروجی اختصاصی است.
تفاوت مهم PIO با GPIO معمولی این است که GPIO صرفاً یک پایه ورودی یا خروجی دیجیتال است، اما PIO میتواند مجموعهای از دستورها را بهصورت مستقل اجرا کند و وضعیت پایههای GPIO را مطابق برنامه تعریفشده تغییر دهد یا بخواند.
برای مثال، فرض کنید بخواهید یک سیگنال دیجیتال با فرکانس مشخص تولید کنید.
در روش نرمافزاری، پردازنده باید دائماً وضعیت پایه را تغییر دهد و برای ایجاد تأخیر از تایمر یا دستورهای تأخیری استفاده کند. اما در روش PIO، میتوانید توالی تغییر وضعیت پایه و تأخیرهای موردنظر را در قالب یک برنامه کوچک تعریف کنید. سپس ماشین حالت PIO آن را اجرا میکند.
به این ترتیب پردازنده میتواند در همان زمان به کارهای دیگری مانند خواندن سنسور، پردازش اطلاعات یا مدیریت ارتباط USB بپردازد.
معماری داخلی PIO در تراشه RP2040
برای درک بهتر عملکرد این واحد، ابتدا باید ساختار سختافزاری آن را بشناسیم.
در RP2040 دو بلوک مستقل PIO وجود دارد:
- PIO0
- PIO1
هر بلوک دارای چهار ماشین حالت مستقل یا State Machine است. بنابراین در مجموع هشت ماشین حالت در اختیار برنامهنویس قرار دارد.
هر State Machine میتواند برنامه اختصاصی خود را اجرا کند و عملیات ورودی و خروجی را مدیریت کند.
اجزای مهم هر ماشین حالت
۱. State Machine
ماشین حالت بخش اجرایی PIO است. این واحد دستورهای برنامه را بهترتیب اجرا میکند و میتواند وضعیت پایههای GPIO را کنترل کند. چهار ماشین حالت هر بلوک میتوانند بهصورت مستقل کار کنند.
۲. Instruction Memory
هر بلوک PIO دارای حافظه دستور مشترک با ظرفیت ۳۲ دستور ۱۶ بیتی است.
نکته مهم این است که این حافظه میان چهار ماشین حالت همان بلوک مشترک است؛ بنابراین نمیتوان برای هر ماشین حالت یک حافظه مستقل ۳۲ دستوری در نظر گرفت.
۳. FIFO
FIFO واسطه انتقال اطلاعات بین پردازنده و ماشینهای حالت است.
هر State Machine دارای FIFO ورودی و خروجی است که بهصورت پیشفرض هرکدام چهار کلمه ۳۲ بیتی ظرفیت دارند. این FIFOها در صورت نیاز میتوانند به یک FIFO هشتکلمهای یکطرفه تبدیل شوند.
۴. Shift Register
هر ماشین حالت دو ثبات انتقال ۳۲ بیتی دارد که برای انتقال سریالی اطلاعات به داخل یا خارج از PIO استفاده میشوند.
۵. Scratch Register
دو ثبات کمکی ۳۲ بیتی به نام X و Y برای انجام عملیات داخلی، شمارش و کنترل حلقهها در برنامه PIO قرار دارند.
این ساختار باعث میشود PIO بتواند بدون استفاده مداوم از پردازنده، عملیات نسبتاً پیچیدهای را روی پایههای ورودی و خروجی انجام دهد.
تفاوت PIO با GPIO، PWM و وقفه
یکی از سؤالات مهم این است که چرا با وجود امکاناتی مانند GPIO و PWM، به PIO نیاز داریم؟
هرکدام از این امکانات کاربرد مشخصی دارند.
| قابلیت | وظیفه اصلی | کاربرد |
|---|---|---|
| GPIO | کنترل ساده ورودی و خروجی دیجیتال | روشن و خاموش کردن LED |
| PWM | تولید سیگنال با فرکانس و Duty Cycle مشخص | کنترل سرعت موتور و روشنایی LED |
| Interrupt | اطلاعرسانی به CPU هنگام وقوع رویداد | تشخیص فشردن کلید |
| PIO | اجرای توالی برنامهریزیشده ورودی و خروجی | ساخت پروتکلهای اختصاصی |
| DMA | انتقال داده بدون نیاز به کپی مداوم توسط CPU | انتقال سریع داده به تجهیزات جانبی |
برای مثال، اگر تنها قصد تولید PWM برای کنترل سرعت یک موتور را دارید، واحد PWM سختافزاری انتخاب سادهتری است.
اما اگر بخواهید یک پروتکل اختصاصی با ترتیب مشخصی از پالسها، خواندن بیتها و زمانبندی دقیق ایجاد کنید، PIO انعطافپذیری بیشتری در اختیارتان قرار میدهد.
نکته مهم این است که PIO جایگزین تمام واحدهای جانبی نیست؛ بلکه مکمل آنهاست.
برنامهنویسی PIO چگونه انجام میشود؟
برنامهنویسی PIO با زبان اسمبلی اختصاصی انجام میشود.
این زبان مجموعه کوچکی از دستورهای تخصصی دارد که برای کنترل پایههای GPIO و انتقال اطلاعات طراحی شدهاند.
دستورهای اصلی PIO عبارتاند از:
| دستور | عملکرد |
|---|---|
JMP |
پرش به یک دستور یا برچسب دیگر |
WAIT |
انتظار برای برقرارشدن یک وضعیت مشخص |
IN |
انتقال اطلاعات از ورودی به ثبات انتقال |
OUT |
انتقال اطلاعات از ثبات انتقال به مقصد |
PUSH |
انتقال اطلاعات به FIFO ورودی پردازنده |
PULL |
دریافت اطلاعات از FIFO خروجی پردازنده |
MOV |
انتقال اطلاعات بین ثباتها یا منابع دیگر |
IRQ |
ایجاد یا پاککردن درخواست وقفه |
SET |
تنظیم مستقیم مقدار پایهها یا ثباتهای X و Y |
یکی از ویژگیهای جالب PIO این است که میتوان به دستورها تأخیر مشخصی اضافه کرد.
برای مثال:
|
1 |
set pins, 1 [9] |
این دستور پایههای خروجی تعریفشده را در وضعیت منطقی یک قرار میدهد و ۹ سیکل تأخیر اضافه میکند. از آنجا که خود دستور نیز یک سیکل اجرا زمان میبرد، اجرای این دستور در مجموع ۱۰ سیکل طول میکشد. این قابلیت برای ایجاد پالسهای دیجیتال با زمانبندی مشخص بسیار کاربردی است.
مثال عملی؛ تولید سیگنال مربعی با PIO
برای درک بهتر عملکرد PIO، یک مثال ساده طراحی میکنیم. هدف این است که Raspberry Pi Pico بتواند روی یکی از پایههای GPIO یک سیگنال مربعی تولید کند.
در این مثال از GPIO شماره 2 استفاده میکنیم.
مرحله اول؛ نوشتن برنامه PIO
ابتدا یک فایل با نام pulse.pio ایجاد کنید و برنامه زیر را داخل آن قرار دهید:
|
1 2 3 4 5 6 |
.program pulse .wrap_target set pins, 1 [9] set pins, 0 [9] .wrap |
این برنامه دو دستور دارد:
- دستور اول خروجی را یک میکند و ۹ سیکل تأخیر اضافه میکند.
- دستور دوم خروجی را صفر میکند و ۹ سیکل تأخیر اضافه میکند.
پس از اجرای دستور دوم، برنامه دوباره به ابتدای حلقه بازمیگردد. در نتیجه یک سیگنال مربعی پیوسته تولید خواهد شد.
مرحله دوم؛ راهاندازی PIO در زبان C
برای استفاده از برنامه PIO در پروژه C، باید آن را در برنامه اصلی بارگذاری و ماشین حالت مربوطه را فعال کنیم.
فایل main.c:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include "pico/stdlib.h" #include "hardware/pio.h" #include "pulse.pio.h" #define PULSE_PIN 2 int main() { PIO pio = pio0; uint sm = pio_claim_unused_sm(pio, true); uint offset = pio_add_program(pio, &pulse_program); pio_gpio_init(pio, PULSE_PIN); pio_sm_config config = pulse_program_get_default_config(offset); sm_config_set_set_pins( &config, PULSE_PIN, 1 ); sm_config_set_clkdiv(&config, 1.0f); pio_sm_set_consecutive_pindirs( pio, sm, PULSE_PIN, 1, true ); pio_sm_init(pio, sm, offset, &config); pio_sm_set_enabled(pio, sm, true); while (true) { tight_loop_contents(); } } |
در این برنامه ابتدا بلوک PIO0 انتخاب میشود. سپس یک ماشین حالت آزاد به پروژه اختصاص داده شده و برنامه PIO در حافظه دستور بارگذاری میشود.
در ادامه پایه GPIO2 به PIO اختصاص پیدا میکند و بهعنوان خروجی تنظیم میشود.
در نهایت ماشین حالت فعال میشود و تولید سیگنال بهصورت مستقل ادامه پیدا میکند.
مرحله سوم؛ تنظیم CMakeLists.txt
برای اینکه فایل اسمبلی PIO هنگام کامپایل به کد قابل استفاده در C تبدیل شود، باید آن را به سیستم ساخت پروژه معرفی کنیم.
در فایل CMakeLists.txt این دستور را اضافه کنید:
|
1 2 3 4 |
pico_generate_pio_header( your_project ${CMAKE_CURRENT_LIST_DIR}/pulse.pio ) |
همچنین کتابخانه سختافزاری PIO باید به پروژه متصل شود:
|
1 2 3 4 |
target_link_libraries(your_project pico_stdlib hardware_pio ) |
در اینجا your_project باید با نام واقعی Target پروژه شما جایگزین شود.
پس از کامپایل، فایل هدر مربوط به برنامه PIO بهصورت خودکار تولید میشود و میتوان آن را در برنامه اصلی استفاده کرد.
مرحله چهارم؛ محاسبه فرکانس خروجی
در این مثال فرض میکنیم فرکانس کلاک سیستم ۱۲۵ مگاهرتز و تقسیمکننده کلاک PIO برابر ۱ باشد.
هر دستور SET در مثال ما ۱۰ سیکل زمان میبرد. بنابراین یک دوره کامل سیگنال شامل ۲۰ سیکل خواهد بود.
فرکانس خروجی از رابطه زیر به دست میآید:
فرکانس خروجی = فرکانس کلاک PIO / تعداد سیکلهای هر دوره
F = 125,000,000 / 20
F = 6,250,000 Hz
بنابراین فرکانس تئوری خروجی برابر ۶٫۲۵ مگاهرتز خواهد بود.
این مقدار یک محاسبه نظری بر اساس تنظیمات فرضشده است و نتیجه اندازهگیری عملی با اسیلوسکوپ نیست.
ارتباط PIO با CPU و DMA
یکی از مهمترین قابلیتهای PIO، امکان انتقال اطلاعات بین ماشین حالت و پردازنده است. برای مثال فرض کنید بخواهید اطلاعات یک سنسور دیجیتال را با سرعت بالا دریافت کنید. در این شرایط میتوانید برنامه PIO را طوری طراحی کنید که اطلاعات را از پایه ورودی دریافت کرده و در FIFO قرار دهد. پردازنده میتواند در زمان مناسب اطلاعات را از FIFO بخواند.
اگر حجم اطلاعات زیاد باشد، میتوان از DMA نیز کمک گرفت. DMA امکان انتقال داده بین حافظه و FIFO را بدون نیاز به اجرای دستور انتقال برای تکتک دادهها توسط CPU فراهم میکند. ترکیب PIO و DMA در پروژههایی که به انتقال سریع و پیوسته اطلاعات نیاز دارند، بسیار کاربردی است.
البته باید توجه داشت که DMA و PIO دو واحد سختافزاری جداگانه هستند و برای استفاده از آنها باید تنظیمات مناسب انجام شود.
کاربردهای عملی PIO در پروژههای الکترونیکی
قابلیت برنامهریزی PIO باعث شده است Raspberry Pi Pico برای پروژههایی که به رابطهای دیجیتال اختصاصی نیاز دارند، گزینه جالبی باشد. چند نمونه از کاربردهای مهم آن عبارتاند از:
۱. پیادهسازی پروتکلهای ارتباطی اختصاصی
اگر پروتکل ارتباطی موردنظر شما در میان رابطهای سختافزاری معمول مانند UART، SPI یا I2C وجود نداشته باشد، میتوانید آن را با PIO پیادهسازی کنید.
۲. کنترل نوارهای LED
برای کنترل LEDهای آدرسپذیر مانند WS2812 به زمانبندی دقیق پالسها نیاز است. PIO میتواند توالی پالسهای موردنیاز را تولید کند و پردازنده را از اجرای مداوم دستورهای زمانبندی آزاد کند.
۳. تولید سیگنالهای تصویری
با استفاده از PIO و سایر قابلیتهای RP2040 میتوان سیگنالهای موردنیاز برای پروژههای تصویری مانند VGA را تولید کرد. البته این کاربرد به طراحی مدار جانبی و مدیریت مناسب دادهها نیز نیاز دارد.
۴. ارتباط با تجهیزات قدیمی
در بعضی پروژهها ممکن است لازم باشد با یک تجهیز قدیمی دارای پروتکل اختصاصی ارتباط برقرار کنید. PIO امکان پیادهسازی بسیاری از رابطهای دیجیتال را فراهم میکند و در چنین شرایطی میتواند جایگزین مناسبی برای طراحی سختافزار اختصاصی باشد.
۵. دریافت سیگنالهای دیجیتال با زمانبندی دقیق
برای خواندن پالسها، اندازهگیری فاصله زمانی بین رویدادها و دریافت دادههای سریالی، میتوان از ماشینهای حالت PIO استفاده کرد.
محدودیتها و نکات مهم هنگام استفاده از PIO
با وجود قابلیتهای فراوان، PIO یک راهکار بدون محدودیت نیست. پیش از استفاده در پروژههای واقعی باید به چند نکته توجه کنید.
۱. حافظه دستور محدود است
هر بلوک PIO تنها ۳۲ دستور ۱۶ بیتی در حافظه مشترک خود دارد. بنابراین طراحی برنامه باید بهینه باشد.
۲. تعداد ماشینهای حالت محدود است
در RP2040 مجموعاً هشت ماشین حالت وجود دارد. اگر پروژه به تعداد بیشتری رابط مستقل نیاز داشته باشد، باید معماری نرمافزار و سختافزار را بازنگری کنید.
۳. برنامهنویسی آن نیازمند یادگیری اسمبلی اختصاصی است
برخلاف GPIO معمولی، برای استفاده مؤثر از PIO باید با دستورهای اسمبلی، FIFO، Shift Register و نحوه تنظیم کلاک آشنا باشید.
۴. PIO جایگزین CPU نیست
پردازنده همچنان مسئول مدیریت کلی برنامه، پیکربندی ماشینهای حالت و پردازش دادههاست. PIO فقط بخشی از وظایف مربوط به ورودی و خروجی را مستقل انجام میدهد.
۵. زمانبندی دقیق به معنی دقت نامحدود نیست
زمانبندی PIO قابل پیشبینی است، اما دقت واقعی سیگنال به فرکانس کلاک، تقسیمکننده، تنظیمات برنامه و محدودیتهای الکتریکی پایهها نیز وابسته است.
جمعبندی
واحد PIO یکی از قابلیتهای مهم و متفاوت تراشه RP2040 است که امکان پیادهسازی رابطها و پروتکلهای دیجیتال اختصاصی را فراهم میکند. با استفاده از این قابلیت میتوان توالیهای دقیق ورودی و خروجی را بدون اجرای مداوم دستورهای مربوط به آن توسط CPU انجام داد.
در این مقاله با معماری PIO، ماشینهای حالت، دستورهای اسمبلی، ارتباط FIFO و DMA و نحوه تولید یک سیگنال مربعی آشنا شدیم. اگر پروژه شما به تولید یا دریافت سیگنالهای دیجیتال با زمانبندی مشخص نیاز دارد و امکانات معمول GPIO، PWM و رابطهای ارتباطی داخلی پاسخگوی نیازتان نیستند، PIO میتواند یکی از گزینههای بسیار کاربردی برای طراحی باشد.



