Press "Enter" to skip to content

Rust روی میکروکنترلر؛ وقتی C می‌فهمه که تنها نیست

Rust روی میکروکنترلراگه تا حالا با میکروکنترلر ها کار کرده باشید، چه Arduino باشه، چه STM32، یا حتی یه چیپ عجیب‌غریب چینی با ۶ تا پا بیشتر از استاندارد، احتمالاً با زبان C یا نهایتاً ++C سروکله زده‌اید. یه دنیای خشن، بدون حافظه‌ی پویا، با اشاره‌گرهایی که اگه حواست نباشه می‌زنن کل پروژه رو به باد می‌دن. حالا تصور کنید یه زبانی بیاد که به شما قدرت C رو بده، ولی جلوی اشتباهات مرگبارتون رو هم بگیره. اینجاست که Rust می‌گه: “اجازه هست وارد شم؟”

Rust در حوزه‌ی برنامه‌نویسی امبدد (embedded) داره روزبه‌روز محبوب‌تر می‌شه. چرا؟ چون هم بدون نیاز به garbage collector کار می‌کنه، هم کاملاً zero-cost abstraction داره، هم حافظه‌ی statically allocated، و از همه مهم‌تر: ایمنی حافظه حتی روی دستگاه‌هایی که فقط ۸ کیلوبایت رم دارن. بله، همون ESP8266 خودمون!

یکی از سوال‌های رایج اینه: “آخه Rust که برای سیستم‌عامل‌ها و سرورها ساخته شده، چطوری می‌خواد روی یه چیپ کوچیک اجرا بشه؟” واقعیت اینه که Rust خیلی قابل تنظیمه. وقتی با #![no_std] پروژه‌تون رو شروع می‌کنید، در واقع می‌گید: «نه، دوست عزیز، من libc و runtime نمی‌خوام. خودم بلدم چطور با آدرس رجیسترها حرف بزنم!» و Rust هم با کمال احترام می‌گه: «چشم.»

ابزارهای بسیار خوبی هم در اختیار برنامه‌نویس‌های embedded هست. مثلاً cortex-m و cortex-m-rt برای Rust روی میکروکنترلر های ARM، یا riscv-rt برای معماری RISC-V. این crateها به شما امکان نوشتن interrupt handlerها، startup code، و مدیریت مموری رو می‌دن ولی به سبک Rust، یعنی امن، تمیز، و بدون اشاره‌گر آویزان.

پروژه‌های ساده برای میکروکنترلر

برای پروژه‌های ساده‌تر یا وقتی تازه شروع کردید، پروژه‌ای مثل embedded-hal می‌تونه نجات‌دهنده‌تون باشه. این یه abstraction کلی برای کار با GPIO، SPI، I2C، PWM و… هست که با هر چیپ پشتیبانی‌شده‌ای قابل پیاده‌سازیه. یعنی یه بار کد می‌نویسید، بعد با تغییر درایور، همون کد می‌تونه روی STM32 یا nRF52 کار کنه. بله، درست شنیدید: قابل‌حمل، بدون دردسر، و بدون #ifdef hell.

یکی دیگه از پروژه‌های دوست‌داشتنی RTIC (Real-Time Interrupt-driven Concurrency) هست؛ یه فریم‌ورک فوق‌العاده برای مدیریت concurrency روی میکروکنترلر، بدون اینکه لازم باشه RTOS جدا نصب کنید. به جای thread و mutex، با task و priority سروکار دارید، و با syntax راحتی می‌تونید سیستم‌هایی بنویسید که چند تا interrupt رو همزمان هندل می‌کنن، بدون اینکه data race داشته باشید.

و البته نمی‌شه از probe-rs نگذشت؛ ابزاری برای debug و flash کردن مستقیم روی میکروکنترلرها. با این ابزار حتی می‌تونید break point بذارید، رجیسترها رو بخونید، و از طریق VSCode یا CLI، پروژه رو زنده بررسی کنید. خلاصه، فقط کامپایل نکنید و دعا؛ می‌تونید واقعاً ببینید چی داره می‌گذره!

شاید بپرسید: «خب پس چرا هنوز همه از C استفاده می‌کنن؟» پاسخ کوتاه اینه: inertia. ابزارهای Rust در حال رشدن، ولی هنوز کامل‌تر از اکوسیستم C++/C نیستن. ولی اگه دارید پروژه‌ی جدیدی از صفر می‌زنید، مخصوصاً اگه بحث امنیت، قابلیت نگهداری یا تایم واقعی مهمه، Rust جدی‌ترین گزینه‌ی آلترناتیو به C در دنیای embedded شده.

و بله، پروژه‌هایی مثل Ferrous Systems, TockOS، یا حتی تلاش‌های رسمی در برخی چیپ‌ست‌های صنعتی نشون می‌دن که این فقط یه علاقه‌ی دانشگاهی یا گیک‌بازی نیست. Rust داره وارد صنعت هم می‌شه، اونم با سرعتی بیشتر از موتورهای درون یه پرینتر سه‌بعدی که اشتباهی 255 فرستاده شده براش.

جمع‌بندی

در نهایت، شاید Rust روی میکروکنترلر هنوز راهی برای طی‌کردن داشته باشه، ولی راهی هست که با اطمینان داره طی می‌شه. اگر شما هم از pointer bugها و interrupt spaghetti خسته شدید، وقتشه با cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart یه پروژه‌ی جدید شروع کنید. قول نمی‌دم راحت باشه، ولی قول می‌دم پشیمون نشید.

2 نظر

  1. حسن حسن 27 اردیبهشت 1404

    سلام و درود. یک نکته برام مبهم بود: در پروژه‌های bare-metal که با #![no_std] و cortex-m-rt نوشته می‌شن، اگه بخوایم از heap استفاده کنیم (مثلاً برای dynamic allocation محدود در runtime)، چه گزینه‌هایی داریم؟ چون تا جایی که دیدم، allocator پیش‌فرضی وجود نداره و malloc هم بی‌معنیه. آیا مثلاً استفاده از alloc-cortex-m یا custom global allocator راه مناسبیه؟ و این کار چقدر با اصول Rust در embedded سازگاره؟

    • حلزون مکانیکی حلزون مکانیکی نویسنده پست | 27 اردیبهشت 1404

      در پروژه‌های bare-metal با #![no_std] به‌طور پیش‌فرض هیچ حافظه‌ی heap یا تخصیص‌دهنده‌ای (allocator) ندارید، چون خود Rust نمی‌تونه فرض کنه که محیط اجرای شما چیزی مثل malloc یا سیستم‌عامل داره. اما شما می‌تونید به‌صورت اختیاری از heap استفاده کنید. البته با رعایت اصول ایمنی و با دقت در مصرف منابع.
      یکی از روش‌های رایج، استفاده از crateی مثل alloc-cortex-m هست که یک global allocator ساده و statically backed فراهم می‌کنه. این crate از یک آرایه‌ی از پیش‌تخصیص‌داده‌شده (معمولاً به صورت [u8; N]) در حافظه استفاده می‌کنه، که در ابتدای برنامه در دسترس قرار می‌گیره، و سپس یک allocator ساده مثل linked-list allocator یا bump allocator روی اون سوار می‌شه.
      در عمل، کاری که باید بکنید:
      یک #[global_allocator] تعریف کنید با استفاده از مثلاً linked_list_allocator::LockedHeap.
      در startup (مثلاً در #[entry]) با فراخوانی ALLOCATOR.lock().init(heap_start, heap_size) heap رو مقداردهی اولیه کنید.
      سپس می‌تونید crate alloc رو به پروژه اضافه کنید و از ساختارهایی مثل Box, Vec, Rc و… استفاده کنید.
      نکته بسیار مهم:
      استفاده از heap در embedded با محدودیت‌های شدید رم همراهه و معمولاً توصیه نمی‌شه مگر با دقت کامل، مثلاً در مواردی که نیاز به مدیریت آبجکت‌های با طول متغیر، یا لایبرری خاصی دارید که بدون heap کار نمی‌کنه.
      از دید فلسفه‌ی Rust در embedded، همچنان استفاده از حافظه‌ی ایستا (static) یا stack، یا تخصیص‌های زمان compile، انتخاب ایمن‌تریه. ولی Rust شما رو محدود نمی‌کنه، به‌جاش به شما ابزار و هشدار لازم رو می‌ده که با مسئولیت کامل خودتون از این امکانات استفاده کنید. کامپایلر همچنان شما رو از نشت حافظه، double free، یا unsafe access نجات می‌ده.
      خلاصه اینکه: بله، استفاده از heap در پروژه‌های no_std ممکن و supported هست، به‌ویژه با alloc-cortex-m یا custom allocator، ولی باید با دقت مصرف حافظه، fragmentation، و زمان تخصیص رو کنترل کنید. در موارد real-time سخت‌گیرانه، شاید بهتر باشه از تخصیص‌های زمان اجرا به کلی پرهیز کنید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *