در قسمتهای پیشین سری آموزش STM32 با توابع HAL، با محیط نرمافزار STM32CubeIDE، پیکربندی پروژه و نحوه دیباگ آشنا شدیم. در این قسمت میخواهیم اولین پروژه عملی سری آموزش را انجام دهیم و به سراغ کد نویسی و استفاده از توابع HAL برویم. در ابتدا در مورد پایههای ورودی خروجی میکرو و جزییات سختافزار GPIO در STM32 صبحت میکنیم و سپس به سراغ استفاده از این پایهها در برنامه و تنظیم پایهها بهعنوان خروجی و یا ورودی برای ارسال یا دریافت فرمان، استفاده خواهیم کرد. با سیسوگ همراه باشی
در میکروکنترلرهای STM32 برای هر پورت همهمنظوره ورودی/خروجی (I/O)، دو رجیستر تنظیم 32 بیتی، یک رجیستر 32 بیتی بهمنظور Set/Reset کردن، یک رجیستر 16 بیتی برای Reset کردن و همچنین یک رجیستر قفل 32 بیتی، وجود دارد. هرکدام از پورتهای I/O، بهطور مستقل قابلبرنامهریزی هستند. بااینحال دسترسی به این پورتها تنها بهصورت یک کلمه کامل (32 بیتی) امکانپذیر است. بدین معنی که نمیتوان تنها به نصف کلمه یا یک بایت از آن دسترسی داشت. رجیسترهای Set/Reset برای هر پورت، امکان دسترسی خواندن/تغییر رجیسترهای GPIO بهصورت Atomic را میدهد، به این معنی که خطر رخ دادن وقفه در بازه زمانی دسترسی و تغییر رجیسترها وجود نخواهد داشت.
در شکل زیر دیاگرام ساختار سختافزار نمونه یک پایه GPIO نشان دادهشده است. در این شکل اجزایی از قبیل دیودهای محافظ، مدارهای pull-up،pull-down، push-pull، مدارهای فعالسازی/غیر فعالسازی حالت خروجی (برای انتخاب ببین ورودی یا خروجی بودن) پایه و همچنین مدار Schmitt-trigger ورودی دیجیتال و آنالوگ، وجود دارند.
ولتاژ کاری برای اکثر پایههای GPIO، 3.3v است. توصیهشده است که در حالت پیشفرض همین ولتاژ کاری را برای پایهها در نظر بگیریم، مگر اینکه در دیتاشیت میکروکنترلر قابلیت تحمل ولتاژ 5v، برای پایهی خاصی قیدشده باشد. در غیر این صورت در صورت اعمال این ولتاژ به پایهها، امکان آسیب دیدن میکروکنترلر وجود دارد. بنابراین کنترل ولتاژ اعمالشده به پایههای ورودی اهمیت زیادی دارد.
علاوه بر محدودیتهای ولتاژ، برای جریان نیز حد مشخصی تعریفشده است. حداکثر جریانی که از پایههای خروجی کشیده میشود یا به آنها هدایت میشود با توجه به دیتاشیت تعریف میشود. معمولاً میزان این جریان، نباید از 25mA تجاوز کند. پس این پارامتر نیز حتماً باید در دیتاشیت میکروکنترلر چک شود و در طراحی و کاربرد لحاظ گردد.
برای پایههای GPIO در stm32 بسته به ورودی یا خروجی بودن آنها، پارامتر سرعت نیز تعریف میشود.
برای هر پایه GPIO، زمانی که در حالت ورودی تنظیمشده باشد، اطلاعات حاضر روی پایه، با هر کلاک بأس (APB2) نمونهبرداری شده و در Input Data Register نوشته خواهد شد. پس برای پایهها ورودی، سرعت نمونهبرداری، بهوسیله سرعت کلاک بأس APB2 تعیین میشود.
در حالتی که یک پایه GPIO، بهعنوان خروجی تنظیمشده باشد، امکان انتخاب سرعت آن وجود دارد. این تنظیم سرعت، بهوسیله تغییر مقدار بیتهای مربوطه در رجیستر تنظیمات GPIO، صورت میگیرد. در جدول زیر حالتهای مختلف پینها و همچنین سرعتهای قابل انتخاب برای پایههای خروجی در میکروکنترلر STM32F103C8، نشان دادهشده است.
در میکروکنترلرهای دیگر، ممکن است سرعتهای متفاوتی برای پایههای خروجی، قابل تنظیم باشد. بهعنوانمثال در میکروکنترلر STM32L432KC، با استفاده از رجیستر کنترل سرعت، میتوان سرعت پایههای خروجی را در 4 حالت سرعتپایین، سرعت متوسط، سرعتبالا و سرعت خیلی بالا تنظیم نمود. این 4 حالت سرعت بسته به پارامترهایی نظیر مقدار VDDio و مجموع ظرفیت خازنی موجود در پایه موردنظر، محدودههای فرکانسی زیر هستند:
سرعت پایین: حدود 10MHz
سرعت متوسط: حدود 50MHz
سرعت بالا: حدود 100MHz
سرعت خیلی بالا: حدود 180MHz
در میکروکنترلرهای STM32، بهمنظور استفاده بهینه از تعداد پایهها در پکیجهای متفاوت و همچنین بهرهگیری از قابلیتهای دستگاههای جانبی مختلف میکروکنترلر، امکان نگاشت یا Remap کردن پایهها وجود دارد. بدینصورت که برای هر پایه مالتی پلکسری وجود دارد که از میان چندین کارکرد و Peripheral مختلف، عملکرد موردنظر را برای پایه انتخاب کرد. به این طریق و با برنامهریزی رجیستر مربوطه، امکان این وجود دارد که عملکرد بعضی Peripheral ها را روی پایههای متفاوتی نگاشت کرد. مزیت این امر در کاربردهایی است که میخواهیم با توجه به پکیج و پایههای در دسترس طراحی خاصی انجام دهیم و یا از دستگاههای جانبی بهخصوصی بهره بگیریم، بدینوسیله ممکن است مسیر کشی بسیار راحتتر صورت گیرد.. همچنین درصورتیکه یک PCB با میکروی متفاوتی طراحیشده باشد و بخواهیم میکروکنترلر مورداستفاده را تغییر بدهیم، به این طریق ممکن است بتوانیم تغییرات موردنیاز در PCB را به حداقل برسانیم. مزیت مهمتر این قابلیت درجایی است که بخواهیم مثلاً مسیر یک سیگنال پرسرعت را جوری تغییر بدهیم که در معرض نویز کمتری قرار بگیرد، که بهوسیله قابلیت توضیح دادهشده، میتوان مسیر مناسبتر برای سیگنال موردنظر، ایجاد کرد.
در ابتدای متن اشاره شد که در میکروکنترلرهای STM32، برای هر پورت معمولاً یک رجیستر 32 بیتی قفل نیز وجود دارد. مکانیزم قفل درجایی که کاربرد دارد که بخواهیم تنظیمات پایههای IO را “قفل” کنیم. یعنی اینکه در صورت اعمال آن، تنظیمات مربوط به پایه موردنظر تا انجام ریست بعدی، قابلتغییر و دستکاری نخواهد بود.
پورتهای همهمنظوره در میکروکنترلر مطابق با مشخصات درجشده در دیتاشیت میکروکنترلر، بهصورت مستقل و در حالتهای مختلفی قابل تنظیماند که در ادامه هرکدام از این حالتها را معرفی میکنیم.
APIهای HAL اصلی و پراستفادهای که برای GPIO تعریف شدهاند، عبارتاند از:
• HAL_GPIO_Init() / HAL_GPIO_DeInit()
• HAL_GPIO_ReadPin() / HAL_GPIO_WritePin()
• HAL_GPIO_TogglePin ()
این API ها به ترتیب برای راهاندازی و غیر فعالسازی، خواندن ورودی و نوشتن در خروجی، و معکوس کردن حالت پین (از یک به صفر و از صفر به یک)، کاربرد دارند.
مانند روالی که برای ایجاد و پیکربندی پروژه گفتیم، یک پروژه جدید ایجاد میکنیم و تنظیمات دیباگ و کلاک آن را مطابق تصاویر زیر انجام میدهیم:
در کتابخانه HAL، تابع تعریفشده برای تغییر پایههای خروجی، GPIO_WritePin نام دارد که در داکیومنت کتابخانه بهصورت زیر تعریفشده است:
همانطور که میبینیم، این تابع سه پارامتر ورودی دارد. ورودی اول پورت GPIO مورداستفاده، ورودی دوم پین موردنظر و ورودی سوم حالتی (منطق صفر یا یک) است که میخواهیم به آن پایه اعمال کنیم.
اکنون از این دستور برای صفر و یک کردن پایه متصل به LED، یعنی PC13 استفاده میکنیم. در تابع int main(void) و قبل از حلقه (1) while کد زیر را مینویسیم:
1 2 3 4 5 | // LED ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); HAL_Delay(1500); // LED OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); |
در این کد، ابتدا ولتاژ پایه PC13 را صفر کردهایم. زیرا LED متصل به این پایه بهصورت Active-low و با سیگنال صفر روشن میشود. سپس بعد از 1.5 ثانیه ولتاژ پایه را 1 و درنتیجه LED را خاموش میکنیم. بعد از نوشتن کد، از پروژه Build میگیریم و سپس برنامه خروجی را روی میکرو Download میکنیم. بدین منظور کلید Run () را میزنیم یا اینکه از منوی Run، گزینه Run را انتخاب میکنیم.
درصورتیکه همه مراحل گفتهشده را بهدرستی طی کرده باشیم، مشاهده میکنیم که پس از ریست شدن و شروع کار میکروکنت، LED موجود روی بورد برای مدت کوتاهی (1.5 ثانیه) روشن و پسازآن خاموش میشود.
در این قسمت قصد داریم یک پایه GPIO دیگر را در حالت ورودی تنظیم کنیم و سپس بهوسیلهی این پایه ورودی یک سیگنال کنترلی دریافت کنیم. سپس بر اساس مقدار این سیگنال کنترلی مشخص میکنیم که LED خاموش و یا روشن باشد. به عبارت سادهتر در این پروژه میخواهیم بهوسیله یک کلید، LED را خاموش و روشن کنیم.
بهجای تشکیل یک پروژه جدید، میتوانیم همان پروژه قبلی را با اعمال تغییراتی برای اضافه شدن ورودی، تنظیم کنیم. مانند شکل زیر یک پایه دیگر GPIO (مثلاً PB12) را در حالت ورودی و بهصورت Pull-up قرار میدهیم:
پس از تنظیم پایه ورودی، مجددا به سراغ کد میرویم.
در کتابخانه HAL، تابع تعریفشده برای تغییر پایههای خروجی، GPIO_ReadPin نام دارد که در داکیومنت کتابخانه بهصورت زیر تعریفشده است:
همانطور که میبینیم، این تابع دو پارامتر ورودی دارد. ورودی اول پورت GPIO مورداستفاده و ورودی دوم پین موردنظر است.
اکنون از این دستور خواندن وضعیت کلید متصل شده به پایه ورودی، یعنی PB12 استفاده میکنیم. در تابع int main(void) و در بدنه حلقه (1) while کد زیر را مینویسیم:
1 2 3 4 5 6 7 8 9 10 11 | /* USER CODE BEGIN 3 */ // Check if the button is pressed if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == 0) { // LED ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); HAL_Delay(250); // LED OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); HAL_Delay(250); } |
در این کد، ابتدا شرط فعال بودن (صفر بودن) کلید بررسی میشود و درصورتیکه کلید فعال باشد، LED برای مدت 250 میلیثانیه، روشن و برای 250 میلیثانیه خاموش میماند. سپس در صورت فعال ماندن کلید این فرآیند تکرار خواهد شد. اکنون بعد از نوشتن کد، از پروژه Build میگیریم و سپس برنامه خروجی را روی میکرو Download میکنیم.
درصورتیکه همه مراحل گفتهشده را بهدرستی طی کرده باشیم، مشاهده میکنیم که درصورتیکه کلید را بزنیم، LED موجود روی بورد شروع به چشمک زدن خواهد کرد.
در این قسمت از سری آموزش STM32 با توابع HAL، اولین برنامه را توسعه دادیم و با عملیات ابتدایی GPIO در stm32 آشنا شدیم. در قسمت بعدی میخواهیم در مورد جزییات بیشتر از GPIO که در این بخش بیان نشدند، صحبت کنیم. با ما همراه باشید.
سلام و تشکر از مطلب مفیدتون
مرسی استاد بابت مطالب مفیدتان
یه سوال داشتم
اگر بخواهیم یک port رو مقدار دهی کنیم یا کامل بخوانیم(نه بصورت pin )) چه باید کرد؟؟
مثلا بخواهیم مقدار 0xA58B را بر ٰروی GPIOA بنویسیم ؟؟؟از چه توابعی باید استفاده کنیم؟؟
سلام دوست عزیز
برای نوشتن مستقیم مقدار روی پورت، رجیستری داریم به نام ODR که با مقدار دهی اون می تونید مقدار مورد مورد نظر رو روی پورت بنوسید
و برای خواندن دایرکت پورت هم از رجیستر IDR استفاده کنید
با عرض سلام و خسته نباشید…
در قسمت “نوشتن کد پروژه برای خواندن پایه ورودی”، GPIO_ReadPin به اشتباه GPIO_WritePin نوشته شده است، لطفا اصلاح گردد. ممنون
ممنونم دوست عزیز اصلاح شد
با سلام
خیلی مفید بود،تشکر از زحمات جنابعالی.
نویسنده شو !
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.