stm32

استفاده از GPIO در میکروکنترلرهای stm32 با استفاده از توابع HAL

خب مثل تمام آموزش های مربوط به میکروکنترلرها که ما هم از اون مستثنی نیستیم اولین آموزش رو با پایه های وروی/خروجی همه منظوره یا اصطلاحا GPIO شروع می کنیم و طبق معمول اولین برنامه ما برنامه چشمکزن یا Blink هست که حکم همون برنامه Hello World رو برای سایر زبان های برنامه نویسی رو داره. اما قبل شروع نوشتن برنامه بیائید با چند تا از توابع HAL برای GPIO آشنا بشیم.

اولین تابعی که میخوایم باهاش آشنا بشیم تابع HAL_GPIO_WritePin هست. فرم کامل این تابع بصورت زیر هست:

همونطور که از نام تابع مشخص هست کار این تابع مقدار دهی یا نوشتن در GPIO هست و چون نوعش void هست مقداری رو برنمی گردونه.

آرگومان اول GPIOx نام پورتی که قرار مقدار دهی بشه که بسته به میکرویی که استفاده می کنید میتونه از GPIOA تا GPIOG متغیر باشه.

آرگومان دوم شماره پین مربوط به GPIO هست. که میتونه مقدار بین GPIO_PIN_0 الی GPIO_PIN_15 و یا GPIO_PIN_ALL ( نوشتن بر روی همه پینها بطور همزمان) باشه.

PinState یک نوع شمارشی یا enum هست که میتونه مقدار GPIO_PIN_RESET یا صفر و مقدار GPIO_PIN_SET معادل ۱ رو داشته باشه.

تابع بعدی HAL_GPIO_ReadPin که به شکل زیر استفاده میشه:

همونطور که از اسم تابع مشخصه کارش خوندن وضعیت یک پین در GPIO هست و با توجه به اینکه مقدار برگشتی از نوع GPIO_PinSate هست خروجی تایع از دو حال خارج نیست. یا GPIO_PIN_RESET معادل ۰ و یا GPIO_PIN_SET معدل ۱٫

آرگومان اول GPIOx نام پورتی که قرار مقدار دهی بشه که بسته به میکرویی که استفاده می کنید میتونه از GPIOA تا GPIOG متغیر باشه.

آرگومان دوم شماره پین مربوط به GPIO هست. که میتونه مقدار بین GPIO_PIN_0 الی GPIO_PIN_15 باشه.

تابع بعدی HAL_GPIO_TogglePin و فرم کلیش به شکل زیر هست:

این تابع مقدای رو برنمی گردونه فقط مقدار پین یک GPIO رو هرچی که هست معکوس میکنه

آرگومان اول GPIOx نام پورتی که قرار مقدار دهی بشه که بسته به میکرویی که استفاده می کنید میتونه از GPIOA تا GPIOG متغیر باشه.

آرگومان دوم شماره پین مربوط به GPIO هست. که میتونه مقدار بین GPIO_PIN_0 الی GPIO_PIN_15 باشه.

تابع بعدی HAL_Delay وفرم کلیش به شکل زیر هست:

این تابع جزو کتابخانه GPIO نیست اما چون قصد دارم ازش استفاده کنم و بسیار پر کاربرد هست اینجا توضیحش میدم. همونطور که از اسمش مشخص هست کار این دستور ایجاد تاخیر هست و چون از نوع void هست قاعدتا مقداری رو برنمی گردونه اما عبارت __weak یعنی چی؟ جلوتر که بریم و به مبحث وقفه ها برسیم این موضوع بیشتر براتون باز میشه. اما تا اینجا همین قدر بدونید که توابعی که از نوع __weak هستن از قبل تعریف شدن. ممکنه داخل اونها کد نوشته شده باشه که بطور پیشفرض کاری انجام بده و ممکنه کدی وجود نداشته باشه. اما این امکان برای کاربر وجود داره که اون تابع رو با همون نام مجددا با کد خودش بازنویسی و شخصی سازی کنه و کامپایلر بهش گیر نده. چون HAL_Delay دارای کد هست و براساس تنطیمات کلاک میکرو برای ما تاخیر ایجاد میکنه الزاما نیازی به باز نویسی نداره کما اینکه میتونه بنا به میل کاربر باز نویسی بشه. جلوتر که به مبحث وقفه ها برسیم توابع از این نوع وجود دارن که داخلشون کد ندارن و فقط تعریف شدن و کاربر موظف هست برای اونها کد بنویسه.

این تابع یک آگومان ورودی بیشتر نداره و اون هم زمان برحسب میلی ثانیه هست.

خب الان به اندازه نوشتن یک برنامه ساده GPIO اطلاعات داریم. پس طبق این آموزش یک پروژه جدید ایجاد کنید. هر نامی که دوست دارید انتخاب کنید مهم نیست. منتها قبل فشار دادن کلید Generate مراحل زیر رو طی کنید اگر هم فراموش کردید و کد رو تولید کردید اشکال نداره در صورت بسته بودن STMCube MX فایل با پسوند *.ioc رو اجرا کنید و یا در صورت باز بودن Cube برگردید و مراحل زیر رو دنبال کنید و مجددا دکمه Generate رو فشار بدید:

پین هایی که قرار هست وضعیت شون رو تغییر بدیم
پین C13 رو در حالت خروجی قرار میدیم
پین A3 رو در حالت ورودی قرار میدیم
وضعیت پین ها بعد از تغییر – لازم به توضیح نیست که پین های PC14 الی PD1 پایه های مریوط به اتصال کریستال هستن
مقاومت pull up مربوط به پین A3 رو فعال می کنیم
برای نوشتن کد برنامه فایل main.c را باز کنید

خب قبل ازاینکه کد برنامه رو دارو کنیم (همچی کدی هم نیست کلا ۵ خط قراره بنویسید) بیائید ببینیم تو فایل main.c چه خبره و کدهایی که فبلا توسط Cube Mx تولید شدن چه کار میکنن.

در ابتدا برنامه هدر فایل main.h به برنامه اضافه شده که در حال حاضر بهش کاری نداریم.

در ادامه نمونه سازی دو تابع SystemClock_Config و MX_GPIO_Init انجام شده و کدهای مربوط به اونها در انتهای فایل main.c توسط Cube MX انجام شده یعنی تمام تنظیماتی که بطور گرافیکی در هنگام تولید پروژه انجام دادین بصورت کد در اونجا قرار گرفته و زحمت شما رو کم کرده.

جلوتر که بریم به تابع اصلی برنامه یعنی main(void) میرسیم. از اینجاست که برنامه وارد فاز اجرایی میشه.

اولین تابعی که بعد main فراخوانی میشه تابع HAL_Init() هست. کد این تابع خارج فایل main.c و در کتابخونه HAL قرار داره و اگر کسی قصد داشته باشه از توابع HAL در برنامه خودش استفاده کنه حتما باید این تابع رو در ابتدای تابع main فراخوانی بکنه. به زبون ساده این تابع تمام امکانات میکرو رو برای شما ریست میکنه.

قبل از اینکه به تابع بعدی برسیم اجازه بدید در مورد فلسفه سیستم کلاک در معماری ARM توضیح مختصری بدم.

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

بعد از تابع HAL_Init() تابع SystemClock_Config اجرا میشه. این تابع تنظیمات کلاک کلی سیستم رو بر اساس اون چیزی در که در Cube MX در هنگام ایجاد پروژه انتخاب کردید، انجام میده. در زیر متن مربوط به این تابع رو مشاهده می کنید:

اگر به تصاویر درج شده در آموزش صفر مراجعه کنید می بینید که برای منابع کلاک سرعت بالا ( HSE ) و کلاک سرعت پایین ( LSE ) انتخاب کردیم که از کریستال خارجی استفاده کنیم که کریستال استفاده شده در برد Bluepill یک کریستال ۸ مگاهرتزی هست که مقدار پیشفرض Cube MX هم همین مقدار بود. همچنین ضریب مدار ضرب کننده ( PLLMul ) رو برابر با X9 قرار دادیم و مقدار مقسم فرکانس ( Prescaler ) رو برای باس APB1 مقدار ۲ و برای APB2 مقدار ۱در نظر گرفتیم. تمام موارد گفته و مواردی که در حالت پیشفرض قرار داشتن در کد تابع SystemClock_Config قرار داره. اگر کمی کد رو مطالعه کنید درکش کار سختی نیست. اگر قرار نبود از Cube MX استفاده کنید تمام موارد فوق رو باید دستی کدنویسی می کردید.

تابع بعدی که در برنامه اجرا میشه تابع MX_GPIO_Init هست. در زیر کد تولید شده برای این تابع رو مشاهده می کنید:

همونطور که از نام این تابع میشه برداشت کرد کار این تابع انجام تنظیمات اولیه پبن های GPIO هست.

برای اینکه بتونیم ازGPIO ها استفاده کنیم علاوه تنطیمات کلاک باسها ، کلاک هر هر کدوم از GPIO ها رو باید فعال کنیم. در همین پروژه ساده سه GPIO درگیر هستن که شامل GPIOA ، GPIOC و GPIOD میشه. بنابراین کلاک این سه بزرگوار باید فعال بشه. این کار توسط این بخش از کد تابع انجام میشه:

اگر به تصویر بالا دقت کنید ما پایه PC13 در حالت خروجی تنظیم همچنین مقدار خصیصه GPIO output level که مشخص کننده سطح پایه خروجی در شروع برنامه هست رو مقدار Low یا صفر منطقی در نظر گرفتیم. در تابع MX_GPIO_Init این کار توسط دستور زیر انجام میشه که در اوایل این آموزش هم توضیح دادیم چطور کار میکنه:

قبل از اینکه شرح کدها رو ادامه بدم بیائید با چند مفهوم دیگه آشنا بشیم. پین های GPIO سه مد کاری داشته دارن. این سه مد عبارتند از OUTPUT یا خروجی، INPUT یا ورودی و Alternate Function. و این آخری چیه؟ خب معلومه … قطعا پین های یک میکرو کنترلر قابلیت های دیگه هم دران که در ادامه آموزش ازشون قرار هست استفاده بکنیم. پس زمانی که میخوایم تنظیمات اون قسمت رو انجام بدیم باید مشخص کنیم GPIO در حالت Alternate Function قرار هست کار بکنه. البته خدا رو شکر این کارها رو قرار نیست دستی انجام بدیم و Cube MX زحمتش رو میکشه ولی دونستنش لازمه. وقتی یک پین در حالت OUTPUT یا Alternate Function قرار داشته باشه به لحاظ پتانسیل خروجی یا میتونه Push-Pull باشه یا Open-Drain. اگر هم ورودی باشه چهار حالت به لحاظ پتانسیل خروجی میتونه داشته باشه که عبارتند از Analog ، Input Floating ، Input pull-down و Input pull-up. (حول نکنید توضیح میدم). تمام مواردی که الان گفتم رو میتونید در جدول زیر که از دیتا شیت کش رفتم رو ببینید:

و اما مفهوم هر کدوم از این حالات چیه (گفتم توضیح میدم):

  • Push-Pull : در این حالت وقتی خروجی قرار هست مقدار High یا یک منطقی داشته ترانزیستور خروجی مربوط به پین، ولتاژ VCC رو به خروجی وصل میکنه و اگر Low یا صفر منطقی باشه GND رو به خروجی وصل میکنه.
  • Open-Drain : در این حالت وقتی خروجی High یا یک منطقی هست پایه در حالت Sink قرار میگیره یعنی میتونه جریان قبول کنه و اگر خروجی Low یا صفر منطقی باشه خروجی Hi-Zi یا امپدانس بالا میشه. به عنوان مثال یک LED رو نظر بگیرید که پایه آندش توسط یک مقاومت به VCC و پایه کاتدش به خروجی میکرو وصل شده. زمانی خروجی میکرو High میشه پایه میکرو حکم GND رو برای کاتد LED بازی می کنه و LED روشن میشه. و زمانی که غیر فعال میشه LED خاموش میشه چون پایه در حالت امپدانس بالا هست و هیچ جریانی ازش عبور نمیکنه. (دیگه از این واضح تر نمی تونستم توصیح بدم)
  • Analog : این حالت زمانی استفاده میشه که بخواهیم از پین به عنوان وروی سیگنال آنالوگ استفاده کنیم. این هم بگم که قبل از استفاده از این خصیصه باید مطمئن بشیم اون پین قابلیت خودن سیگنال رو داره یا نه چون همه پین ها قابلیت آنالوگ ندارن.
  • Input-floating: در این حالت پین به هیچ جا وصل نیست و معمولا طراح مدار خودش اون رو مدیریت میکنه و اگر در حالتی که جایی وصل نباشه و خونده بشه مقداری ناپایدار خواهد داشت.
  • Input-pull down: توسط مقاومت داخل میکرو به GND وصل میشه و در صورتیکه به جایی وصل نباشه و مقدارش خونده بشه مقدار Low رو برمی گردونه
  • Input-pull up: در این حالت توسط مقاومت داخل میکرو پین به VCC متصل میشه و در صوتیکه به جایی وصل نباشه و مقدارش خونده بشه مقدار High رو برمی گردونه.

میدونم تا الان حتما خوابتون گرفته اما فقط یک موضوع دیگه رو میگم و میریم برای کد نویسی برنامه. و اون موضوع چیه؟ سرعت. برعکس میکرو کنترلرهایی مثل AVR، در معماری ARM این امکان برای شما وجود داره که سرعت هر پین GPIO رو وقتی در حالت OUTPUT قرار داره به طور مستقل کنترل کنید. اکثر ما با شنیدن کلمه سرعت حس خوبی بهمون دست و اگر این موضوع در مورد میکرو کنترلر باشه همه میگیم خب بذارش روی آخرین سرعت اما قبل اینکه این کار رو انجام بدیم باید چند تا نکته رو مد نظر قرار بدیم. اول یک نگاهی به تصویر زیر داشته باشید:

سرعت GPIO رابطه مستقیمی با Slew Rate داره؟ اصلا Slew Rate چیه؟ مدت زمانی هست که یک سیگنال از مقدار Low به High و یا برعکس میرسه. به زمان Low به High میگیم T Rise و به زمان High به Low میگیم T Fall. هر چی سرعت GPIO بالاتر باشه مقدار Slew Rate بیشتر میشه و افزایش Slew Rate موجب افزایش نویز EMI میشه که محاسبه میزانش از حوصله این بحث خارج هست. موضوع دیگری که باید بهش توجه کرد با افزایش سرعت GPIO توان مصرفی میکرو هم افزایش پیدا میکنه و در جایی که دستگاه شما از باطری استفاده میکنه ممکنه براتون تولید دردسر کنه پس معمولا با در حالت معمول سرعت GPIO رو در حداقل مقدار ممکن قرار میدیم مگر اینکه واقعا دلیل قانع کننده ای برای این کار داشته باشیم. تنظیم پارامتر سرعت در خانواده های میکروهای ARM شرکت ST کمی متفاوت هست:

همونطور که تصویر بالا مشاهده می کنید تنظیم سرعت GPIO برای خانواده های میکروهای ARM شرکت ST با دو مقدار دهی متفاوت انجام میشه. اگر مقدار سرعت GPIO در حداقل مقدار ممکن قرار بگیره فرکانس خروجی حداکثر میتونه ۸ مگاهرتز و اگر روی حداکثر باشه این سرعت به بالای ۱۰۰ مگاهرتز میرسه. برای خانواده F4/L4 این فرکانس بالای ۱۵۰ مگاهرتز هست. پس همونطور که کد مربوط به تابع MX_GPIO_Init مشاهده می کنید سرکت پایه PC13 روی حداقل مقدار ممکن قرار گرفته.

خب به اندازه کافی تئوری گفتم. برای شروع در فایل main.c کد زیر رو بعد از عبارت تایپ کنید /* USER CODE BEGIN 3 */ تایپ کنید. معمولا وقتی می خواهیم کدمون رو در حلقه While بنویسیم اون رو بعد از این عبارت و قبل از عبارت /* USER CODE END 3 */ قرار میدیم.

بعد از وارد کردن کد مدار زیر رو ببندید. برای تغذیه مدار از پروگرمارتون پایه تغذیه اش از مدار خارج نشده اتصال چهار پین پروگرامر کافی هست ولی از پروگرامرتون پایه تغذیه نداره میتونید از اتصال یک شارژر اندروید برای تغذیه مدار استفاده کنید. اگر شاژر خم ندارید یک برد تغذیه ۳٫۳ ولتی تهیه کنید و به پایه ۳٫۳v و GND برد متصل کنید. همچنین میتونید از آی رگولاتور ۵ ولت مثل ۷۸۰۵ استفاده کنید و خروجی اون رو به پایه های ۵v و GND متصل کنید. وقتی اتصلات کامل شد در نرم افزار Keil ابتدا کلید F7 رو فشار بدید و اجازه بدید برنامه کامپایل بشه و بعد کلید F8 رو برای آپلود کد بر روی میکرو استفاده کنید. تو این برنامه ما فعلا به دکمه که به برد متصل کردیم کاری نداریم و تو مثال بعد ازش استفاده می کنیم. بعد از اینکه برنامه رو آپلود کردید دکمه ریست روی برد رو فشار بدید. در صورتیکه مراحل رو درست طی کرده باشید می بینید LED متصل به پایه PC13 با ترخ ۱ ثانیه ای روع به چشمک زدن می کنه. تبریک اولین برنامه تون رو روی میکرو های ST نوشتید. برنامه ساده هست نیاز به توضیح خاصی نداره با استفاده از دستوراتی که در ابتدای پست آموزش دادم یک بار پایه PC13 رو در حالت High قرار میدیم و بعد یک تاخیر یک ثانیه ای ایجاد میکنیم. بعد پایه PC13 رو در حالت Low قرار می دیم و مجددا یک تاخیر یک ثانیه ای ایجاد می کنیم. و این سیکل تا زمان تغدیه مدار ادامه پیدا میکنه (ننوشتم تا ابد … آدم واقع بینی هستم) فقط الان یک لطفی به خودتون و بقیه بکنید. نرید الان تو رزومه تون بنویسید مسلط به برنامه نویسی میکروهای ST !!! هنوز خیلی کار داریم.

راستی کد بالا رو با دستور HAL_GPIO_TogglePin هم میشه نوشت:

حالا بریم سراغ مثال بعدی . میتونید یا کد قبلی رو کامنت کنید یا کلا پاکش کنید و کد زیر رو وارد کنید:

کار خاصی نکردیم. یک متغیر از نوع GPIO_PinState به نام pin_state تعریف کردیم و مقدار رو برابر با HAL_GPIO_ReadPin قرار دادیم. این دستور مقدار پایه PA3 رو میخونه و در pin_state قرار میده. خط بعدی هم که نیازی به توضیح نداره مقدار pin_state رو به پایه PC13 که LED بهش وصل هست منتقل میکنه. با هر بار فشار دادن دکمه، LED روشن و با رها کردنش خاموش میشه.

خب جلسه GPIO اینجا تموم میشه. تو جلسه بعدی میخوام در مورد وفقه های GPIO بنویسم.

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

ساعد

6 دیدگاه

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


2 + یک =