آموزش میکروکنترلر STM32F4 – قسمت دهم ;
در قسمت نهم آموزش میکروکنترلر STM32F4 به کتابخانه HAL پرداختیم. در این قسمت از آموزش میکروکنترلر STM32F4 به سیستم عامل زمان واقعی (RTOS) و مبحث Thread میپردازیم. با سیسوگ همراه باشید.
سیستم عامل زمان واقعی (RTOS)
سیستمهای عامل زمان واقعی معمولا در سیستمهای تعبیه شده (embedded system) مورد استفاده هستند. امروزه چون در سیستمهای تعبیه شده هم نیاز به انجام کارهای متنوعی به شکل هم زمان است گاهی مجبور به استفاده از سیستمهای عامل زمان واقعی هستیم. برای مثال میکروکنترلر باید چند ورودی را چک کند و با توجه به ورودی و سایر رویدادها، خروجیهایی تولید کند. وقتی تعداد این ورودی/خروجیها زیاد باشد برنامهای که از روش سرکشی (pooling) استفاده کند، جواب مناسبی نخواهد داد. یا خیلی کند خواهد بود، یا برای حالتهای ویژهای که برای ما مهم هستند پاسخ با سرعت یا کیفیت مناسب نخواهد داد.
رابط برنامهنویسی سیستم عامل زمان واقعی CMSIS
این رابط برنامهنویسی، لایهای است بین کاربر و یک سیستم عامل زمان واقعی که کار را برای او راحت تر میکند. همانطور که در شکل زیر دیده میشود لایه صورتی رنگ میانی، رابط CMSIS-RTOS بین سیستم عامل و کاربر است.
سیستم عامل توسط شخص سوم ارائه میشود که ممکن است رایگان یا پولی باشد. از نمونههای این سیستم عامل میتوان به FREERTOS و RTX اشاره کرد. لایه ی CMSIS-RTOS تابعهای کمینه، برای راهاندازی یک سیستم بر مبنای سیستم عامل را فراهم میکند ولی ممکن است سیستم عامل دارای امکانات بیشتری باشد که برای استفاده از آنها باید مستقیم از دستورات خود سیستم عامل استفاده کرد.
آشنایی با FREERTOS
در برنامههای تک Thread، دادههای برنامه و پشته اصلی نیز در ابتدا و انتهای RAM قرار دارند. با اجرای برنامه، پشته با رفتن به تابعها به پایین رشد میکند و با خروج از تابعها به بالا فشرده میشود. در برنامههای چند Threadی هر Thread، حافظه پشتهی خود را در RAM دارد. در شکل زیر این را می بینیم.
ناحیه داده در هنگام لینک کردن برنامه به شکل ایستا اختصاص مییابد. این ناحیه شامل متغیرهای عمومی و ایستاست. ناحیه بالای این ناحیه برای اختصاص دهی پویا به کار میرود. این ناحیه را ناحیه heap مینامند و توسط تخصیصدهنده حافظه گسترش مییابد. پشته اصلی در انتهای حافظه قرار دارد و به پایین رشد میکند. در FREERTOS پشتههای Threadها به شکل بلوکهایی در ناحیه heap قرار دارند. آزمونهایی وجود دارد تا مطمئن باشیم که در هنگام اجرا ریسمانها، فراتر از فضایی که در اختیار دارند، نروند.
در این سیستم عامل به Threadها، Task گفته میشود. یک وظیفه یک حلفه بی پایان است که کاری را انجام میدهد. به عبارتی تابع Task، بازگشتی ندارد.
برای اجرای Task، باید آن را توسط یک تابع خلق کرد. برعکس کدهای معمولی که یک تابع، با فراخوانی اجرا میشود، در سیستم عامل، Task با ساختن، اجرا میشود. تابعی که فرایند ساختن یک Task را انجام میدهد به شکل زیر است.
در این تابع پارامترها به ترتیب عبارتند از نام تابع Task-نامی که به Task میدهیم- (مثلا در بالا نام آن ThreadFunction بود)، اندازه پشته، اشارهگر به پارامترهای انتخابی، اولویت و دستگیره ی Task ساخته شده.
در FreeRTOS یک Task به نام بیکار نیز وجود دارد که وقتی هیچ Task دیگری برای اجرا نباشد اجرا میشود. این Task کمترین مقدار الویت را دارد. ساختار کلی برنامهای که چند Threadی نوشته شده باشد به شکل زیر است.
همانطور که دیده میشود ابتدا آغازسازی سخت افزار انجام میشود. برای راه اندازی این سیستم عامل روی STM32 باید وقفههای NVIC را با ۱۶ اولویت و بدون زیر الویت آغازسازی کرد. سپس وظایف ساخته میشود و در نهایت تابع زمانبند شروع به کار میکند. این تابع هیچ بازگشتی ندارد، اگرچه تابعی در API برای خروج از آن وجود دارد.
یک برنامه نمونه با دو Thread در شکل زیر دیده میشود. برنامه دو Thread دارد که دو LED را چشمک زن میکنند:
برای دانلود این سیستم عامل میتوانید به این آدرس مراجعه کنید. با نصب نرم افزار Kail، فایلهای header این سیستم عامل نیز در پوشههای آن موجود است. راه دیگر استفاده از نرم افزار CubeMX است که به شکل گرافیکی تنظیمات اولیه و اختصاص پینها را برای میکروکنترلر ST شما انجام میدهد. در این نرمافزار میتوانید این سیستم عامل را نیز به پروژه خود اضافه کنید تا پروژهای که ساخته میشود شامل سیستم عامل هم باشد. اگر قصد داشته باشید به شکل دستی این عملیات را انجام دهید باید تعدادی از فایلها را در پروژه خود استفاده کنید.
فایل های کلید این سیستم عامل در شکل زیر دیده میشود:
ابزارهای همزمانسازی
تصور کنید دو Thread به یک منبع مشترک نیاز داشته باشند. برای مثال دو Thread قصد دارند از USART کاراکتری دریافت کنند. کدی که برای این کار استفاده میشود به شکل زیر است.
این Threadها باید رجیستر وضعیت USART را بخوانند تا هر وقت خالی نبود و کاراکتری دریافت شده بود، آن را از رجیستر داده بخوانند. اگر Thread ۱ رجیستر وضعیت را بخواند و بفهمد که رجیستر داده خالی نیست ولی این Thread، در همین زمان، توسط Thread ۲ قبضه (preempt) شود، در این صورت Thread 2، رجیستر وضعیت را بررسی میکند و داده را میخواند. سپس Thread ۱ از سرگیری میشود و اطلاعات آن از رجیستر وضعیت دیگر معتبر نیست و اگر دادهای را از رجیستر داده بخواند نادرست خواهد بود.
برای حل این مشکل میتوان USART را کاملا در اختیار Thread ۱ قرار داد. یک راه جلوگیری از قبضه کردن، غیرفعال کردن وقفه هاست که کاری است به وضوح اشتباه، چون ممکن است تا مدتی کاراکتری نیاید یا با تاخیر بیاید. چیزی که نیاز است، ساز و کاری است که Threadهایی که برای رقابت به USART شرکت دارند، بلوکه شوند. این راه حل، با استفاده از سمافورها (Semaphore) قابل انجام است. سمافور در واژه به معنی جفت پرچم های کوچکی است که برای علامت دادن و ارتباط از راه دور به کار میرفته است. از همین کاربرد، در مفهوم سیستم عامل استفاده شده است.
سمافور یک نخستینه همزمانسازی استاندارد است که برای ساختن سیستم عامل نیاز بود. اینها برای دسترسیِ ایمنِ Thread به منابع مشترک به کار میروند. یک Thread که نیاز به منبع دارد یا اجازه دسترسی به آن را مییابد یا توسط زمان بند scheduler بلوکه میشود تا منبع رها شود. وقتی یک Thread یک منبع را رها میکند ممکن است به عنوان یک اثر جانبی یک Thread را از بلوکه در آورد.
این سیستم عامل سه نوع سمافور را پشتیبانی میکند که عبارتند از موتکس ها، سمافورهای باینری و سمافورهای شمارنده. در اینجا از یک موتکس استفاده میکنیم. موتکس یک بلیت token دارد. اگر موتکس آزاد باشد با دستور take آن را میگیریم اگر هم آزاد نباشد تابعی که درخواست token کرده است بلوک میشود و در فهرست انتظار قرار میگیرد. دستور give توکن را بازیابی میکند. تفاوت اصلی بین موتکسها و سمافورهای شمارنده در این است که دومی میتواند چند token داشته باشد.
میتوان یک موتکس دیگر برای حفاظت تابع putchar استفاده کرد تا از رقابتهای داده مانند بالا جلوگیری کرد. اما این نتیجه مطلوبی نخواهد داشت. تصور کنید چند Thread تابع putchar را از طریق فرایندی به نام putstring که در زیر مشاهده میکنید صدا بزنند. در این حالت ممکن است دو Thread همزمان روی خروجی بنویسند.
رابط نرمافزاری RTOS به کمک CMSIS
همانطور که گفته شد CMSIS یک رابط نرمافزاری برای کار کردن با سیستمهای عامل زمان واقعی مختلف ارائه کرده است. یک مزیت این رابط، یکپارچه شدن نرمافزارها و قابلیت انتقال بین سیستمهای عامل مختلف است. در زیر این رابط نرمافزاری، جزئیتر بررسی میشود. با استفاده از این رابط ممکن است شما از سیستمهای عامل مختلفی استفاده کنید ولی کد یکسانی در همه آنها استفاده کنید. برای مثال در نرمافزار Kail شما میتوانید یکی از سیستم عامل FREERTOS و RTX را در برنامه خود استفاده کنید ولی تابعهایی که به کار میبرید تابعهای CMSIS است. تنها با اضافه کردن فایل هدر cmsis_os.h و فایلهای سیستم عامل مورد نظرتان، میتوانید شروع به کار کنید.
Threadها
تابع main یک تابع Thread ویژه است که در ابتدای راهاندازی سیستم شروع میشود و الویت آغازین آن برابر osPriorityNormal میباشد. Threadها میتوانند در حالتهای زیر باشند:
- اجرایی: Threadی که در حال اجرا است. هر لحظه از زمان تنها یک Thread میتواند در حال اجرا باشد.
- آماده: Threadهایی که آماده اجرا هستند. وقتی Thread در حال اجرا پایان یافت یا به حالت انتظار رفت، Thread منتظر بعدی با بالاترین اولویت به حالت اجرا می رود.
- انتظاری: Threadهایی که منتظر یک رویداد هستند تا اجرا شوند در حال انتظار می باشند.
- غیرفعال: Threadهایی که ساخته نشده اند یا پایان یافته اند در حالت نافعال هستند. این Threadها معمولا منابع سیستم را استفاده نمیکنند.
ساختار این حالت ها و تغییر وضعیت بین آن ها در شکل زیر دیده میشود.
تابع هایی در CMSIS برای کار کردن با Threadها در نظر گرفته شده است. این تابع ها عبارتند از:
- oSThreadCreate :که یک Thread میسازد و آن را به Threadهای فعال اضافه میکند و وضعیتش را آماده قرار می دهد.
- oSThreadGetId : شماره شناسایی Threadی که در حال حاضر در حال اجراست باز می گرداند.
- oSThreadTerminate : اجرای یک Thread را متوقف میکند و آن را از Threadهای فعال خارج میکند.
- oSThreadSetPriority : اولویت یک Thread فعال را تغییر می دهد.
- oSThreadGetPriority : اولویت جاری یک Thread فعال را دریافت میکند.
- oSThreadYield : کنترل را به Thread بعدی که در حالت آماده است می دهد.
Threadها دارای یکی از حالتهای اجرا شونده، آماده، منتظر و غیرفعال هستند.
یک ساختمان شمارنده، اولویت Thread را مشخص میکند. اولویتهای ممکن برای 7 حالت است که عبارتند از بیکار، پایین، کمتر از عادی، عادی، بیشتر از عادی، بالا، زمان واقعی. این ساختمان شمارنده به این شکل تعریف شده است.
1 2 3 4 5 6 7 8 9 10 | enum osPriority { osPriorityIdle = -3, osPriorityLow = -2, osPriorityBelowNormal = -1, osPriorityNormal = 0, osPriorityAboveNormal = +1, osPriorityHigh = +2, osPriorityRealtime = +3, osPriorityError = 0x84 } |
ماکروهای تعریف شده
دستور زیر ساختن یک Thread را با مشخص کردن تابع، اولویت، تعداد نمونه های مجاز و پشته اختصاص یافته، نشان میدهد:
1 | #define oSThreadDef(name, priority, inSTances, STacksz) |
دستور زیر دسترسی به یک Thread تعریف شده را نشان میدهد:
1 | #define oSThread(name) &os_thread_def_##name |
تابع تأخیر osDelay
فراخوانی تابع osDelay، تِرِد فراخواننده را به مدت مشخص شده بر حسب میلیثانیه، در حالت انتظار قرار میدهد. در این مدت، زمان بند (scheduler) سیستم عامل، Threadهای دیگر که در حالت آماده هستند اجرا میکند.
در قسمت یازدهم آموزش میکروکنترلر STM32F4 به ادامه RTOS و مباحث سمافور و موتکس خواهیم پرداخت. با سیسوگ همراه باشید.
بی صبرانه منتظر ادامه این مطلب هستیم.
سلام دوست عزیز
ممنون از لطف شما
سلام
تشکر از شما برای این مطلب بسیار مفید
سلام
ممنون از لطف شما
سلام
با تشکر از وقتی که گذاشتید و زحمتی که کشیدید و این مطلب را آماده کردید.
1- میتونید منابعی برای مطالعه بیشتر و درک سمافور به زبان فارسی معرفی کنید؟
2- آنچه که در این قسمت نوشتید همه از تجربه و مطالعه خودتون هست یا از منابع مشخصی دارید استفاده میکنید؟
سلام دوست عزیز
در قسمت بعدی این مجموعه به سمافور پرداخته میشه.
این مجموعه که حاصل تحقیقات و تجربیات آقای مرادمند هست در اختیار سیسوگ قرار گرفته تا برای عموم به اشتراک گذاشته بشه.
ممنون از همراهی شما