در قسمتهای پیشین سری آموزش STM32 با توابع HAL، با محیط نرمافزار STM32CubeIDE، پیکربندی پروژه و نحوه دیباگ آشنا شدیم. در این قسمت میخواهیم اولین پروژه عملی سری آموزش را انجام دهیم و به سراغ کد نویسی و استفاده از توابع HAL برویم. در ابتدا در مورد پایههای ورودی خروجی میکرو و جزییات سختافزار GPIO در STM32 صبحت میکنیم و سپس به سراغ استفاده از این پایهها در برنامه و تنظیم پایهها بهعنوان خروجی و یا ورودی برای ارسال یا دریافت فرمان، استفاده خواهیم کرد. با سیسوگ همراه باشی
پایههای ورودی خروجی همه منظوره (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 در stm32 بسته به ورودی یا خروجی بودن آنها، پارامتر سرعت نیز تعریف میشود.
سرعت پایه GPIO در حالت ورودی
برای هر پایه GPIO، زمانی که در حالت ورودی تنظیمشده باشد، اطلاعات حاضر روی پایه، با هر کلاک بأس (APB2) نمونهبرداری شده و در Input Data Register نوشته خواهد شد. پس برای پایهها ورودی، سرعت نمونهبرداری، بهوسیله سرعت کلاک بأس APB2 تعیین میشود.
سرعت پایه GPIO در حالت خروجی
در حالتی که یک پایه GPIO، بهعنوان خروجی تنظیمشده باشد، امکان انتخاب سرعت آن وجود دارد. این تنظیم سرعت، بهوسیله تغییر مقدار بیتهای مربوطه در رجیستر تنظیمات GPIO، صورت میگیرد. در جدول زیر حالتهای مختلف پینها و همچنین سرعتهای قابل انتخاب برای پایههای خروجی در میکروکنترلر STM32F103C8، نشان دادهشده است.
در میکروکنترلرهای دیگر، ممکن است سرعتهای متفاوتی برای پایههای خروجی، قابل تنظیم باشد. بهعنوانمثال در میکروکنترلر STM32L432KC، با استفاده از رجیستر کنترل سرعت، میتوان سرعت پایههای خروجی را در 4 حالت سرعتپایین، سرعت متوسط، سرعتبالا و سرعت خیلی بالا تنظیم نمود. این 4 حالت سرعت بسته به پارامترهایی نظیر مقدار VDDio و مجموع ظرفیت خازنی موجود در پایه موردنظر، محدودههای فرکانسی زیر هستند:
سرعت پایین: حدود 10MHz
سرعت متوسط: حدود 50MHz
سرعت بالا: حدود 100MHz
سرعت خیلی بالا: حدود 180MHz
انجام عملیات بیتی Atomic روی پایههای GPIO
استفاده از وقفه خارجی GPIO در STM32
انتخاب پایههای دستگاههای جانبی (Peripheral Pin Select)
در میکروکنترلرهای STM32، بهمنظور استفاده بهینه از تعداد پایهها در پکیجهای متفاوت و همچنین بهرهگیری از قابلیتهای دستگاههای جانبی مختلف میکروکنترلر، امکان نگاشت یا Remap کردن پایهها وجود دارد. بدینصورت که برای هر پایه مالتی پلکسری وجود دارد که از میان چندین کارکرد و Peripheral مختلف، عملکرد موردنظر را برای پایه انتخاب کرد. به این طریق و با برنامهریزی رجیستر مربوطه، امکان این وجود دارد که عملکرد بعضی Peripheral ها را روی پایههای متفاوتی نگاشت کرد. مزیت این امر در کاربردهایی است که میخواهیم با توجه به پکیج و پایههای در دسترس طراحی خاصی انجام دهیم و یا از دستگاههای جانبی بهخصوصی بهره بگیریم، بدینوسیله ممکن است مسیر کشی بسیار راحتتر صورت گیرد.. همچنین درصورتیکه یک PCB با میکروی متفاوتی طراحیشده باشد و بخواهیم میکروکنترلر مورداستفاده را تغییر بدهیم، به این طریق ممکن است بتوانیم تغییرات موردنیاز در PCB را به حداقل برسانیم. مزیت مهمتر این قابلیت درجایی است که بخواهیم مثلاً مسیر یک سیگنال پرسرعت را جوری تغییر بدهیم که در معرض نویز کمتری قرار بگیرد، که بهوسیله قابلیت توضیح دادهشده، میتوان مسیر مناسبتر برای سیگنال موردنظر، ایجاد کرد.
مکانیزم قفل GPIO در STM32
در ابتدای متن اشاره شد که در میکروکنترلرهای STM32، برای هر پورت معمولاً یک رجیستر 32 بیتی قفل نیز وجود دارد. مکانیزم قفل درجایی که کاربرد دارد که بخواهیم تنظیمات پایههای IO را “قفل” کنیم. یعنی اینکه در صورت اعمال آن، تنظیمات مربوط به پایه موردنظر تا انجام ریست بعدی، قابلتغییر و دستکاری نخواهد بود.
تنظیمات GPIO
پورتهای همهمنظوره در میکروکنترلر مطابق با مشخصات درجشده در دیتاشیت میکروکنترلر، بهصورت مستقل و در حالتهای مختلفی قابل تنظیماند که در ادامه هرکدام از این حالتها را معرفی میکنیم.
تنظیم پایه به عنوان خروجی
- خروجی به صورت Open-Drain
- خروجی به صورت Push-Pull
تنظیم پایه به عنوان ورودی
- ورودی مدار باز یا فلوت (Hi-Z)
- ورودی در حالت Pull-Up
- ورودی در حالت Pull-Down
تنظیم در حالت عملکرد ثانویه (Alernate Function)
- عملکرد ثانویه در حالت Push-Pull
- عملکرد ثانویه در حالت Open-Drain
تنظیم در حالت آنالوگ
جمع بندی نکات GPIO
- همانطور که اشاره شد، همهی پایههای GPIO تحمل ولتاژ 5v را ندارند و اکثر پایهها تنها تا 3.3v را تحمل میکنند.
- هر پورت GPIO را که بخواهیم در هرکدام از حالتهای کاری مورداستفاده قرار دهیم، ضروری است که ابتدا کلاک آن را فعال کنیم.
- گفته شد که برای پایههایی که در حالت خروجی تنظیمشده باشند، سرعت تغییرات یا سوییچ، قابل تنظیم است. این سرعت معمولاً در سه حالت سرعتپایین، سرعت متوسط و سرعتبالا قابل تنظیم است.
- برای پایههای ورودی اشاره شد سرعت بأس APB2، تعیینکننده نرخ نمونهبرداری تمامی پایههای GPIO تنظیمشده در حالت ورودی است.
- برای پایه GPIO، امکان قطع کردن (یا High impedance) کردن پایه وجود دارد. بدین منظور، پایه بهعنوان ورودی و در حالت Hi-Z تنظیم میشود.
- هنگامیکه در یک پروژه میخواهیم تنظیمات پایههای GPIO، بعد از راهاندازی سیستم، قابلتغییر نباشد، میتوان با استفاده از مکانیزم قفل، تنظیمات پایههای موردنظر را ثابت نگه داشت.
- در مورد وقفه خارجی، گفته شد که برای تمامی پورتها و پایههای GPIO، این نوع وقفه از بخش EXTI، قابل فعالسازی است.
API های GPIO در HAL
APIهای HAL اصلی و پراستفادهای که برای GPIO تعریف شدهاند، عبارتاند از:
• HAL_GPIO_Init() / HAL_GPIO_DeInit()
• HAL_GPIO_ReadPin() / HAL_GPIO_WritePin()
• HAL_GPIO_TogglePin ()
این API ها به ترتیب برای راهاندازی و غیر فعالسازی، خواندن ورودی و نوشتن در خروجی، و معکوس کردن حالت پین (از یک به صفر و از صفر به یک)، کاربرد دارند.
تنظیم پایه GPIO در حالت خروجی و ارسال سیگنال دیجیتال
ایجاد پروژه GPIO Output
مانند روالی که برای ایجاد و پیکربندی پروژه گفتیم، یک پروژه جدید ایجاد میکنیم و تنظیمات دیباگ و کلاک آن را مطابق تصاویر زیر انجام میدهیم:
نوشتن کد پروژه برای کنترل پایه خروجی
در کتابخانه 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 در حالت ورودی و دریافت سیگنال دیجیتال
در این قسمت قصد داریم یک پایه GPIO دیگر را در حالت ورودی تنظیم کنیم و سپس بهوسیلهی این پایه ورودی یک سیگنال کنترلی دریافت کنیم. سپس بر اساس مقدار این سیگنال کنترلی مشخص میکنیم که LED خاموش و یا روشن باشد. به عبارت سادهتر در این پروژه میخواهیم بهوسیله یک کلید، LED را خاموش و روشن کنیم.
تنظیم پروژه GPIO Input
بهجای تشکیل یک پروژه جدید، میتوانیم همان پروژه قبلی را با اعمال تغییراتی برای اضافه شدن ورودی، تنظیم کنیم. مانند شکل زیر یک پایه دیگر 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 نوشته شده است، لطفا اصلاح گردد. ممنون
ممنونم دوست عزیز اصلاح شد
با سلام
خیلی مفید بود،تشکر از زحمات جنابعالی.