در قسمت پیشین از سری آموزش STM32 با توابع HAL، در مورد ریست و کلاک میکروکنترلر و نحوه تنظیم واحد RCC توضیح دادیم. در این قسمت، میخواهیم نحوه دیباگ در STM32CubeIDE را یاد بگیریم، باما همراه باشید.
برای دیباگ کردن به یک دستگاه دیباگر نیاز داریم که ما در این آموزش از پروگرمر و دیباگر ST Link استفاده خواهیم کرد. بااینحال دیباگرهای دیگری نیز وجود دارند که از طریق پورت SWD یا JTAG و با عملکردی مشابه ST Link میتوانند برای پروگرام و دیباگ مورداستفاده قرار گیرند. در این بخش از آموزش، در مورد نحوه تنظیم دیباگر در پیکربندی پروژه، اتصال آن به بورد و چگونگی استفاده از آن را یاد میگیریم. اما قبل از آن، میخواهیم در مورد پشتیبانی ARM Cortex-M3 (یعنی پردازنده به کاررفته در میکروکنترلر موردنظر ما) از قابلیت دیباگ در STM32CubeIDE، صحبت کنیم.
دیباگر میکروکنترلر، دستگاهی است که این امکان را به ما میدهد که اجرای برنامه را در هر قسمت دلخواهی متوقف کرده تا متغیرهای مختلف را بررسی کنیم. بسته به نوع دیباگر مورداستفاده، میتوانیم تعداد مشخصی Break Point در برنامه قرار دهیم تا هنگام دیباگ، اجرا در این نقاط متوقف شود. علاوه بر این در حالت دیباگ میتوانیم برنامه را خط به خط اجرا کنیم و تغییرات را زیر نظر بگیریم.
پردازندههای ARM Cortex M3 & M4، به دلیل بهرهگیری از افزونههای سختافزاری دیباگ، امکان پشتیبانی از قابلیتهای دیباگ پیشرفته رادارند. افزونههای گفتهشده، به پردازنده این قابلیت را میدهند که در زمان فچ کردن دستور (اصطلاحاً breakpoint) یا دسترسی به داده (watchpoiont) متوقف شود. در این توقفها، حالت داخلی هسته پردازشی و همچنین حالت بیرونی سیستم، قابلبررسی هستند. درنهایت و پس از بررسی حالتهای سیستم، میتوان پردازنده را به حالت کار عادی و ادامه اجرای برنامه، بازگرداند.
این قابلیتهای دیباگ، بااتصال میکروکنترلر به دستگاه دیباگر قابلاستفاده هستند. در ابتدای این قسمت، گفتیم که برای دیباگ معمولاً از دو پورت JTAG و یا SWD (Serial Wire Debug) استفاده میشود. در این قسمت آموزش، برای دیباگ میکروکنترلر موجود روی بورد Blue Pill، یعنی STM32f103c8 از پورت SWD استفاده میکنیم.
در شکل زیر، دیاگرام سختافزار مربوط به دیباگ در پردازنده ARM Cortex-M3 نشان دادهشده است. در این شکل میتوان دید که سختافزار گفتهشده، شامل ویژگیهای مختلفی (ازجمله ITM) است که امکان دیباگ پیشرفته را فراهم میکنند.
همانطور که در شکل مشخص است، این سختافزار شامل بخشهای زیر میشود:
- SWJ-DP: Serial wire / JTAG debug port
- AHP-AP: AHB access port
- ITM: Instrumentation trace macrocell
- FPB: Flash patch breakpoint
- DWT: Data watchpoint trigger
- TPUI: Trace port unit interface (available on larger packages, where the corresponding pins are mapped)
- ETM: Embedded Trace Macrocell (available on larger packages, where the corresponding pins are mapped)
سختافزار دیباگر ST-Link v2
گفتیم که برای دیباگ به سختافزار ST-Link v2 نیاز داریم. در شکل زیر مدل مورداستفاده ما (مدل اصلی سمت راست) و کپی چینی آن (سمت چپ) نشان دادهشده است. قابلذکر است که نسخه کپی از قابلیت serial wire trace debugging پشتیبانی نمیکند. زیرا برخلاف نسخه اصلی، در pinout آن، پایه SWO وجود ندارد. در نسخه اصلی این پایه به پایه B3 بورد BluePill متصل میشود.
درواقع حالت serial wire trace debugging یا trace asynchronous sw به ما این امکان را میدهد که از طریق پایه SWO، از میکرو LOG بگیریم. از این طریق برای چاپ و مشاهده مقدار متغیرها و حالت میکرو، از UART و سختافزارهای مبدل بینیاز خواهیم بود.
برای تغییر مدل کپی و اضافه کردن پایه SWO، میتوان بهصورت دستی این پایه را از روی میکرو به یک پایه متصل کرد. بهعنوانمثال در شکل زیر، SWO به پایه 5v متصل شده است. اگرچه میتوان از هر پایه دیگری (از میان 10 پایه موجود) نیز استفاده کرد.
اکنونکه تا اندازه با مفهوم دیباگ و جزییات سختافزاری آن آشنا شدهایم، میخواهیم به سراغ ساخت یک پروژه برویم و سپس در محیط STM32CubeIDE مراحل دیباگ یک کد نمونه را باهم بررسی کنیم.
ایجاد یک پروژه نمونه
برای اینکه مراحل دیباگ را بررسی کنیم، نیاز به یک پروژه نمونه داریم. پس طبق مراحل شرح دادهشده در قسمت قبلی، یک پروژه جدید میسازیم. تمام تنظیمات را در حالت اولیه رها میکنیم و تنها در تب Pinout & Configuration و در بخش System Core -> SYS نوع اتصال دیباگ را روی Serial Wire و یا Trace Asynchronous Sw تنظیم میکنیم. توجه کنید که در حالتی که Trace Asynchronous Sw انتخابشده باشد پایه PB3 به SWO اختصاص خواهد یافت.
اکنون وارد بخش کد نویسی و دیباگ میشویم. بدین منظور از فایلهای پروژه (در پنجره Project Explorer در سمت چپ)، از مسیر Core -> Src، main.c را انتخاب میکنیم. در این فایل کدهای برنامه اصلی نوشته میشوند و توابع نوشتهشده در سایر فایلها فراخوانی خواهند شد. در این پروژه به دلیل کم بودن حجم کد موردنظر، تمام کد را در همین فایل مینویسیم.
نوشتن کد و شروع دیباگ
اکنون میخواهیم در فایل main.c کد سادهای بنویسیم تا بتوانیم چگونگی دیباگ شدن پروژه را باهم بررسی کنیم. قبل از آن نگاهی به کد تولیدشده توسط نرمافزار در هنگام پیکربندی پروژه میاندازیم. ساختار این کد بدینصورت است که قبل از تابع int main(void)، توابع موردنیاز برای راهاندازی بخشهای مختلف میکرو اعلانشده و سپس درون تابع int main(void) فراخوانی میشوند. در این پروژه تنها تابع استفادهشده void SystemClock_Config(void) است که همانطور که از نام آن مشخص است برای اعمال تنظیمات کلاک بهکار میرود. درون بدنه تابع int main(void)، تابع دیگری نیز فراخوانی شده است که HAL_Init نام دارد. این تابع طبق تعریف کتابخانه HAL، در شروع هر برنامهای فراخوانی میشود تا سه عمل را انجام دهد؛
- راهاندازی Cache مربوط به دادهها و دستورات و شروع فرایند pre-fetch
- تنظیم تایمر Sys Tick برای ایجاد وقفه در هر 1 میلیثانیه (بر اساس کلاک HSI) با کمترین اولویت وقفه و همچنین راهاندازی تایمر Sys Tick
- فراخوانی تابع HAL_MspInit برای راهاندازی سیستم.
در پروژههای آینده و با استفاده از دستگاههای جانبی بیشتر، کدهای بیشتری نیز توسط نرمافزار تولید میشوند تا تنظیمات انجامشده در زمان پیکربندی را برای ما اعمال کنند. اکنون به سراغ کد نویسی میرویم. بدین منظور در بدنه تابع (void)int main و قبل از حلقه (1) while، متغیر counter را تعریف میکنیم و مقدار اولیه آن را برابر با 0 میگذاریم؛
/* USER CODE BEGIN 2 */ int counter = 0; /* USER CODE END 2 */
سپس در بدنه حلقه (1) while، مقدار متغیر counter را در هر بار تکرار افزایش میدهیم و سپس بهاندازه 500ms تأخیر ایجاد میکنیم؛
/* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ counter++; HAL_Delay(500); } /* USER CODE END 3 */
اکنون باید کد نوشتن شده را compile کنیم و از پروژه build بگیریم. بدین منظور روی آیکون چکش در نوارابزار بالای صفحه کلیک کنیم. نکتهای که در این قسمت وجود دارد، امکان انتخاب میان دو حالت Debug و Release است. تفاوتی که میان این دو حالت وجود دارد این است که کد تولیدشده در حالت Release، بهصورت بهینه ساخته میشود و درنتیجه بسیار کمحجمتر و اجرای آن سریعتر است. بااینحال، ازآنجاییکه در این حالت، بخشهایی از کد حذف یا دچار تغییر میشوند، دیباگ کد تولیدشده با مشکلات و دشواریهایی روبرو میشود. پس برای دیباگ کردن از حالت Debug و برای تولید کد خروجی پروژه از حالت Release، استفاده خواهد شد.
باوجود نکات گفتهشده، برخی توصیه میکنند که در حالت تولید کد خروجی نیز تنظیم Build روی حالت Debug قرار بگیرد. زیرا ممکن است در حالت Debug برنامه به خوبی اجرا شود اما در حالت Release، باگهای جدیدی ایجاد شوند. البته این توصیه برای زمانی است که ازنظر حجم کد و سرعت اجرا محدودیت خاصی نداشته باشیم و کد تولیدی در حالت Debug از این نظر قابلقبول باشد.
پس از Build گرفتن از پروژه، وارد حالت دیباگ میشویم. البته قبل از آن باید مطمئن شویم که دیباگر بهدرستی به سیستم متصل شده و شناختهشده است. همچنین میکروکنترلر باید به دیباگر متصل باشد. بهتر است آخرین ورژن فریمور دیباگر نیز نصبشده باشد. در رابطه با مورد آخر، خود برنامه STM32CubeIDE، در زمان اتصال دیباگر پیغامی برای آپدیت کردن فریمور دستگاه، خواهد داد.
برای رفتن به حالت Debug میتوانیم از نوارابزار بالای صفحه آیکون حشره مانند دیباگ را انتخاب کنیم یا اینکه از منوی Run، Debug را انتخاب کنیم؛
بعد از رفتن به حالت دیباگ پنجرههای جدیدی اضافه میشوند و همچنین نوارابزار دچار تغییراتی خواهد شد. در پنجره اضافهشده در سمت چپ برنامه و در تب متغیرها، میتوانیم متغیر counter و مقدار اولیهی آن را مشاهده کنیم.
اکنون در خطی که مقدار متغییر counter افزایش مییابد، یک Breakpoint اضافه میکنیم. برای این کار، میتوانیم در سمت چپ این خط کد، دبل کلیک کنیم. یا اینکه در همین قسمت کلیک راست کنیم و سپس گزینه Add Breakpoint را انتخاب کنیم.
درنهایت کلید مربوط به شروع اجرای کد (
) را انتخاب میکنیم. مشاهده میکنیم که اجرای برنامه تا همان خطی که Breakpoint قرار دارد، انجامشده و در همانجا متوقف میشود. با کلیک کردن دوباره روی کلید شروع اجرا یا Resume، اجرای برنامه ادامه پیدا میکند و مجدداً روی همین دستور متوقف میشود. اکنون در پنجره مربوط به متغیرها در پنجره سمت چپ میبینیم که مقدار متغیر counter، یک واحد افزایشیافته است؛
به همین ترتیب با ادامه دادن به اجرای کد، مقدار متغیر counter به 2، 3 و بالاتر افزایش پیدا خواهد کرد.
همانطور که مشاهده میشود، یکی از کاربردهای مهم حالت دیباگ و استفاده از Breakpoint، توقف اجرای در یک خط مشخص از برنامه و نظارت بر مقدار متغیرها و حالت سیستم است. البته برنامهی حاضر بیشازحد ساده بوده و تنها شامل یک متغیر میشود. در آینده و در پروژههای پیچیدهتر، پارامترهای بسیار بیشتری را میتوانیم در حالت دیباگ، موردبررسی قرار دهیم. بهعنوانمثال یک بخش دیگر که در حالت دیباگ میتواند موردبررسی قرار گیرد، رجیسترهای پردازنده هستند. برای بررسی رجیسترها، در پنجره سمت چپ، تب Registers را انتخاب میکنیم؛
علاوه بر این، میتوانیم مقدار ذخیره شده در رجیسترهای خاص منظوره پردازنده و همچنین رجیسترهای کنترل Peripheralهای میکروکنترلر را در تب SFRs در همین پنجره، مشاهده کنیم؛
در این پروژه چون از پریفرال خاصی استفاده نکردهایم وارد جزییات این قسمت نمیشویم. بااینحال در تمامی پروژهها درصورتیکه عملکرد موردنظر را از میکروکنترلر و دستگاههای جانبی دریافت نکنیم، میتوانیم به سراغ بررسی این بخش برویم.
در آخر میخواهیم کاربرد بقیه کلیدهای پراستفادهی نوارابزار اضافهشده در حالت دیباگ را توضیح دهیم. کلیدهای اصلی مورداستفاده ما در حالت دیباگ عبارتاند از:
پایان دادن به اجرا و شروع دوباره برنامه
شروع و یا ادامه دادن به اجرای برنامه
توقف کردن اجرای برنامه
پایان دادن به اجرای برنامه و خروج از حالت دیباگ
رفتن به درون بدنه تابع (یا اجرای یک خط کد و رفتن به خط بعد، در صورتی که آن خط کد، یک دستور ساده بوده و فراخوانی یک تابع نباشد)
اجرای یک خط از کد و رفتن به خط بعدی
بیرون آمدن از بدنه تابع(در صورتی که وارد بدنه یک تابع شده باشیم)
در این قسمت ابزارها و بخشهای مهم مربوط به دیباگ در محیط نرمافزار STM32CubeIDE را معرفی کردیم. در قسمت بعدی سری آموزش STM32 با توابع HAL، میخواهیم به GPIO و تنظیم پایهها در حالت ورودی و خروجی بپردازیم. با ما همراه باشید.
منبع: سیسوگ