ARM, STM32_LL, آموزش, توصیه شده, مقاله

آموزش STM32 با توابع LL قسمت پنجم: GPIO-Output

آموزش STM32 با توابع LL

در قسمت چهارم با واحد RCC آشنا شدیم و جزئیات و دلیل وجود کلاک در مدارات دیجیتال را بررسی کردیم، همچنین گفتیم که کلاک ورودی به میکروکنترلر چگونه در میکروکنترلر با استفاده از PLL افزایش و با استفاده از Prescaler کاهش می‌یابد. در ادامه مدار Reset که برای ریست کردن میکروکنترلر استفاده می‌شود را معرفی کردیم و گفتیم که با ریست کردن میکروکنترلر عملا در برنامه چه اتفاقی خواهد افتاد و این عمل باعث چه چیزی خواهد شد. در نهایت هم یک برد آموزشی بسیار ساده اما کاربردی به اسم blue pill board را برای پیش‌برد ادمه‌ی آموزش به شما معرفی کردیم.

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

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

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

اما در این قسمت قصد داریم چه مواردی را بررسی کنیم؟

اگر به خاطر داشته باشید در قسمت سوم یک کد برای GPIO نوشتیم و به شما قول دادیم که کد نوشته شده را در قسمت مربوط به GPIO به طور کامل بررسی و تحلیل کنیم.

پس مشخصا در این قسمت قصد داریم که در رابطه با GPIO صحبت کنیم.

اما آن بررسی و تحلیل‌هایی که گفتیم قرار است نقشه‌ی راه را برای شما مشخص و ادامه‌ی مسیر را هموار کند شامل چه چیزهایی می‌شود؟

ما در ابتدا کد GPIO با توابع LL را می‌نویسم و توضیح خواهیم داد که چگونه باید از این توابع در برنامه استفاده کرد. سپس همین کد GPIO را با استفاده از توابع HAL خواهیم نوشت و در هر دو حالت سرعت پین GPIO که خروجی کردیم را اندازه‌گیری و مقایسه می‌کنیم.

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

خب اکنون اجازه بدهید کمی در رابطه با خود GPIO و این که چه چیزی هست صحبت بکنیم.

GPIO (General-purpose input/output)

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

به کدی که در قسمت سوم نوشتیم دقت کنید:

اولا که این کد در حلقه‌ی while برنامه نوشته شده است، یعنی مداوم یک پین از میکروکنترلر 0 و 1 خواهد شد.

تابع LL_GPIO_SetOutputPin، برای High کردن پینی از میکروکنترلر که از قبل به عنوان خروجی تنظیم شد (همان تنظیماتی که در نرم‌افزار STM32CubeMX انجام می‌دادیم در واقع پین را به عنوان خروجی تنظیم می‌کرد) به کار می‌رود.

بیایید به تعریف تابع برویم، ببینیم که در بدنه‌ی تابع چه اعمالی انجام شده است که باعث می‌شود یک پین خروجی High شود.

به تعریف تابع توجه کنید:

در تعریف تابع فقط عبارت زیر وجود دارد:

حال باید به تعریف تابع WRITE_REG برویم، تا ببینیم که این تابع چه کاری انجام می‌دهد:

با توجه به تعریف بالا، این تابع ورودی دوم را در ورودی اول خود قرار می‌دهد.

ورودی دوم، عبارتی شامل 0 و 1 منطقی است و ورودی اول یک رجیستر از GPIO است، یعنی رجیستر BSRR. پس برای High کردن یک پین میکروکنترلر، باید مقداری متناظر با همان پین در رجیستر BSRR نوشته شود.

این تابع بر اساس مستندات میکروکنترلر نوشته شده است. در مستندات گفته شده است که برای High کردن یک پین از میکروکنترلر باید در بیت متناظر با آن در رجیستر BSRR مقدار 1 منطقی قرار داده شود.

حال اگر شما در عبارت (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU) که همان ورودی دوم تابع است به جای PinMask یکی از مقادیری که در توضیحات تابع گفته شده است را قرار بدهید، و عبارت بالا را محاسبه کنید می‌بینید که بیت متناظر با آن پین از میکروکنترلر که قرار است High شود مقدار 1 منطقی را دارد. ما در اینجا به جای PinMask مقدار LL_GPIO_PIN_0 را قرار دادیم.

توضیحات تابع کجاست؟

دو روش برای پیدا کردن توضیحات تابع وجود دارد، یک روش خواندن فایل Description of STM32F1 HAL and low-layer drivers – User manual، که هم توابع LL را توضیح می‌دهد و هم توابع HAL را. روش دیگر در نرم‌افزار است، قبل از تعریف هر تابع، در فایل مربوط به آن، توضیحات آن تابع نیز وجود دارد که می‌توانید از این توضیحات استفاده بکنید.

مثلا در نرم‌افزار برای تابع LL_GPIO_SetOutputPin، قبل از تعریف تابع، توضیحات زیر آورده شده است:

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

مثلا برای اینکه PA0 مقدارش High شود باید عبارت LL_GPIO_PIN_0 به عنوان دومین پارامتر در تابع قرار بگیرد.

البته شما تنها با خواندن توضیحات تابع نیز می‌توانید یک واحد را راه‌اندازی کنید و نیازی به اینکه در تابع چه چیزی نوشته شده است ندارید، اما بررسی تعریف و جزئیات تابع و همینطور ارتباطش با سخت‌افزار و رجیسترها درک شما را عمیق‌تر می‌کند.

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

پس از تابع LL_GPIO_SetOutputPin به تابع LL_mDelay می‌رسیم که به اندازه‌ی عددی که در ورودی‌اش قرار می‌گیرد بر حسب ms تاخیر ایجاد می‌کند. چون این تابع بر اساس systick timer نوشته شده است در این قسمت جزئیات این تابع را بررسی نمی‌‎کنیم و فقط با نحوه‌ی عملکردش که همان تاخیر به میزان عدد ورودی بر حسب ms است، آشنا می‌شویم.

تابع دیگر، تابع GPIO_ResetOutputPin است که برای Low کردن پینی از میکروکنترلر که از قبل به عنوان خروجی تنظیم شد به کار می‌رود.

به بدنه‌ی تابع توجه کنید:

این تابع همان عملیات تابع LL_GPIO_SetOutputPin را انجام می‌دهد، اما بر روی رجیستر BRR. برای Low کردن پین میکروکنترلر باید مقداری متناظر با همان پین در رجیستر BRR قرار داده شود.

جزئیات این تابع به دلیل مشابهت با تابع LL_GPIO_SetOutputPin بررسی نمی‌شود.

پس تا اینجا نتیجه می‌گیریم که برای High کردن پین باید در رجیستر BSRR و برای Low کردن آن باید در رجیستر BRR مقدار 1 منطقی را در بیت متناظر با پین موردنظر نوشت.

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

این بار به کد GPIOای که با توابع HAL نوشته شده است توجه کنید:

ابن کد دقیقا عملکرد همان کدی را دارد که با توابع LL نوشتیم.

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

سناریوی تست به این صورت است که می‌خواهیم سرعت 0  و 1 شدن یک پین از میکروکنترلر را با استفاده از لاجیک آنالایزر رصد کنیم.

در واقع ما تابع Delay را به این دلیل که چشم قادر به دیدن 0 و 1 شدن پین میکروکنترلر بر روی LED باشد، به کار بردیم. حال که قرار است با استفاده از لاجیک آنالایزر نتیجه را رصد کنیم دیگر نیازی به تابع Delay نیست.

و نکته‌ی دیگر اینکه هر سری که شرط حلقه‌ی while چک می‌شود باید زمانی صرف شود. همین زمانی که صرف چک کردن شرط حلقه‌ی while می‌شود، بر روی سناریوی تست اثر بد می‌گذارد. برای رفع این مشکل کدی که درون حلقه‌ی while می‌نویسیم باید به صورت زیر باشد:

در واقع ما با تکرار کد، اثر زمانِ بررسی شرط حلقه‌ی while را کم می‌کنیم. ما در بالا فقط سه بار کد را تکرار کردیم، اما اگر خودتان خواستید تست را انجام بدهید باید تعداد تکرار را بسیار بیشتر باشد.

در هر دو حالت؛ برنامه‌ی نوشته شده را بر روی حافظه‌ی Flash میکروکنترلر دانلود می‌کنیم و با استفاده از لاجیک آنالایزر سرعت پین میکروکنترلر را اندازه می‌گیریم.

ابتدا به تصاویر زیر دقت کنید:

 

توابع HAL

توابع HAL

توابع LL

توابع LL

 

همانطور که مشاهده می‌کنید، سرعت با توابع LL تقریبا 5.6 برابر بیشتر از توابع HAL است!

آیا این امر اتفاقی بوده است؟ مشخص است که خیر.

چندین عامل مختلف باعث شده است که سرعت تا این اندازه بیشتر شود، در ادامه همه‌ی این عوامل را بررسی می‌کنیم.

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

اگر به برنامه‌ی نوشته شده با توابع LL توجه بکنید، می‌بینید که برای 0 و 1 کردن پین خروجی دو تابع LL_GPIO_SetOutputPin و LL_GPIO_ResetOutputPin به کار رفته است و در این دو تابع از دو رجیستر BSRR و BRR استفاده شده است. اما در برنامه‌ی نوشته شده با توابع HAL همین کار را تنها با یک تابع به اسم HAL_GPIO_WritePin انجام داده است و در ابن تابع فقط از رجیستر BSRR استفاده شده است!

کار این دو رجیستر چیست؟ رجیستر BRR یک رجیستر 16 بیتی است که برای 0 کردن پین‌های میکروکنترلر به کار می‌رود. و رجیستر BSRR یک رجیستر 32 بیتی است که 16 بیت اول آن برای 1 کردن و 16 بیت دوم آن برای 0 کردن پین‌های میکروکنترلر به کار می‌رود.

 

رجیستر BRR

رجیستر BRR

رجیستر BSRR

رجیستر BSRR

 

در واقع ST گفته است که برای 1 کردن پین‌های میکروکنترلر از 16 بیت اول رجیستر BSRR و برای 0 کردن پین‌های میکروکنترلر از رجیستر BRR استفاده کنید، اما اگر به هر دلیلی نمی‌خواهید که از رجیستر BRR استفاده بکنید، هم 0 کردن و هم 1 کردن پین‌ها با استفاده از رجیستر BSRR امکان‌پذیر است.

با توضیحات و عکس‌های بالا نتیجه می‌گیریم که با وجود رجیستر BSRR، دیگر نیازی به رجیستر BRR نیست. بله این نتیجه درست است، اما به هزینه‌ی اینکه سرعت را فدا کنیم. کاری که دقیقا در توابع HAL انجام شده است.

ابتدا به پیاده‌سازی تابع HAL_GPIO_WritePin توجه کنید:

در این تابع با استفاده از یک شرط می‌گوید که اگر ورودی سوم یعنی PinState برابر با GPIO_PIN_RESET نبود پس قصد 1 کردن پین میکروکنترلر است و مقدار ورودی دوم یعنی GPIO_Pin باید در 16 بیت اول رجیستر BSRR قرار بگیرد، در غیر این‌صورت هدف صفر کردن پین میکروکنترلر است و باید مقدار ورودی دوم در 16 بیت دوم رجیستر BSRR قرار بگیرد.

خب اولا که در این تابع از دستورات شرطی استفاده شده است و ما می‌دانیم که بررسی هر دستور شرطی خود مستلزم صرف زمان است، از سمتی دیگر در این دستور شرطی از دستور شیفت بیتی (GPIO_Pin << 16U) نیز استفاده شده است که این دستور هم صرف زمانی دیگر را می‌طلبد.

پس تا اینجا استفاده از دستورات شرطی و شیفت بیتی از عوامل کاهش سرعت در توابع HAL محسوب می‌شوند.

عامل دیگر این اختلاف سرعت این است که توابع LL به صورت توابع معمولی پیاده‌سازی نشده‌اند، بلکه این توابع به صورت INLINE پیاده‌سازی شده‌اند.

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

سرعت توابع INLINE بیشتر از توابع معمولی است و اگر دقت کرده باشید در تعریف توابع LL از عبارت STATIC_INLINE__ استفاده شده است که نشان از INLINE بودن این توابع دارد.

پس سه عاملی که باعث اختلاف سرعت فاحش شدند، شامل دستورات شرطی و شیفت بیتی و توابعی که به صورت INLINE پیاده‌سازی شدند، بودند. البته عوامل تاثیرگذار دیگر مثل ساختارهای شی‌گرائی که در توابع HAL به وفور و بیشتر از توابع LL یافت می‌شود نیز وجود دارد که بنا به اهمیت کمتر از عوامل ذکر شده به آن‌ها پرداخته نشده است.

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

در قسمت ششم در رابطه با GPIO در حالت Input صحبت خواهم کرد.

انتشار مطالب با ذکر نام و آدرس وب سایت سیسوگ، بلامانع است.

شما نیز میتوانید یکی از نویسندگان سیسوگ باشید.   همکاری با سیسوگ

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

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

14 دیدگاه در “آموزش STM32 با توابع LL قسمت پنجم: GPIO-Output

  1. سعید گفت:

    16u یعنی چی؟
    چه فرقی با 16 می کنه؟

    1. سعید جان، وقتی شما یک عدد صحیح رو بدون پسوند می‌نویسید دیفالت به صورت integer، و وقتی یک عدد اعشاری را بدون پسوند می‌نویسید دیفالت به صورت double در نظر گرفته می‌شود.

      حال ما بنا به نیاز؛ که می‌تونه مدیریت حافظه، جلوگیری از تبدیلات ضمنی، بهینه‌سازی برنامه و … باشه می‌تونیم پسوندی به این اعداد بدهیم تا به آن صورت دیفالتی که توضیح دادم در نظر گرفته نشوند. مثلا در همینجا وقتی از حرف “U” استفاده می‌کنیم به معنای unsigned است.

  2. پارسا گفت:

    سلام من الان ی کم میکروst رو بلدم اما مشکلی که دارم اینه که مثلا برای ساخت pwm تو یوتیوب فیلم هست مثل اون انجام میدم ولی مثلا در حالت one pulse فیلمی نیست من نمی تونم راش بندازم یعنی نمی دونم باید از چه مستنداتی از شرکت استفاده کنم لطفا راهنمایی کنید چطوری باید ی قسمت از میکرو رو راه بندازم بدون این که دید قبلی از اون واحد داشته باشم یعنی هیچی ندونم ازش اما ی فایلی که به تر تیب بگه باید چه کار کنم

    1. سلام پارسای نازنینم.

      بهترین منبع برای یادگیری هر میکروکنترلری، خواندن مستنداتی است که شرکت سازنده‌ی آن میکروکنترلر ارائه می‌دهد.

      هر میکروکنترلر دارای مستندات زیادی است، اما شما در وهله‌ی اول تنها به بعضی از آن‌ها نیاز پیدا خواهید کرد.
      مثلا در میکروکنترلرهای شرکت ST در وهله‌ی اول تنها به سه تا از آن‌ها یعنی دیتاشیت، رفرنس منوال و دیسکریپشن توابع نیاز دارید.

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

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

      خب شما تا الان با خواندن رفرنس منوال متوجه شدید که چگونه باید یک واحد را راه‌اندازی کنید، اما اینکه با چه ابزاری این کار را انجام بدهید هنوز مشخص نیست!
      راه‌های غیر معقول زیادی وجود دارند که من در این‌جا نام نمی‌برم، اما سه راه معقول رجیستری، توابع LL و توابع HAL وجود دارند که شما می‌توانید از این راه‌ها استفاده کنید.

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

  3. Navidh475 سید نوید گفت:

    درود خوب بود ولی باید بیشتر میرفتی تو جزئیات برنامه نویسی باGpio

    1. درود نوید نازنین. بله جزئیات بسیار بیشتری وجود داره که میشه به اونا پرداخت اما خب در وهله‌ی اول گفتن این نکات ضرورتی نداره و باعث شلوغی و سردرگمی میشه.

      اما خب سعی می‌کنم در ویدئوهایی که به موازات این مجموعه آموزشی تدوین می‌کنم، نکات بیشتری را توضیح بدم.

      1. Navidh475 سید نوید گفت:

        خدا خیرتون بده اگ ویدئو های و مقالات بیشتری در مورد میکروARMبزارید ممنون میشم

  4. محمد گفت:

    واقعا چرا من زودتر با سیسوگ آشنا نشدم 🙁
    احساس می‌کنم زندگیم رو هدر دادم.
    بسیار آموزنده بود.
    سپاس.

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

  5. Amir گفت:

    خیلی جالب و آموزنده بود.
    منتظر قسمت بعدی هستیم.
    خدا قوت … ممنون.