وضعیت اعتبارسنجی: پروژه‌های دارای باگ و اصلاح‌شده واقعاً با CubeMX 6.14.0، DB.6.0.140، CubeF3 1.11.6 و GCC 13.2.1 تولید و در 3 ساخت پاک با -Wall -Werror بدون خطا و هشدار کامپایل شدند. تحلیل آفلاین ELF و اسکریپت پیش‌بینی حلقه اجرا شده‌اند. پروگرام برد، نشست CubeIDE، مشاهدهٔ Breakpoint و دریافت SWO انجام نشده‌اند؛ تمرین‌ها و مقادیر سخت‌افزاری این مقاله راهنما و انتظار قابل بررسی هستند.

پس از درس 6؛ ساخت برنامه و پروگرام با ST-LINK، در این درس یک خطای منطقی عمدی را با توقف اجرا و بررسی متغیرها پیدا می‌کنیم. هدف، شناخت ابزار دیباگ و ساختن فرضیه از وضعیت برنامه است؛ کامپایل موفق به‌تنهایی درستی منطق را نشان نمی‌دهد.

معماری دیباگ، مسیرهای سخت‌افزاری و تفکیک SWD از SWO

ارتباط دیباگر با هستهٔ ARM Cortex-M4 بر پایهٔ درگاه سخت‌افزاری CoreSight پیاده‌سازی شده است. در برد توسعهٔ GebraBit GB100STV2 (مبتنی بر تراشهٔ STM32F303CBT6 در بسته‌بندی LQFP48)، سیگنال‌های رابط دیباگ دوسیمه (Serial Wire Debug یا SWD) شامل خطوط دیتای دوطرفهٔ SWDIO روی پین PA13، پالس ساعت کلاک SWCLK روی پین PA14، خط ریست NRST و ارجاع ولتاژ تغذیهٔ 3V3 به همراه پین مشترک GND هستند. دیباگر ST-LINK از این مسیر دوسیمه برای کنترل کامل اجرای پردازنده، خواندن و نوشتن حافظه‌ها، توقف هسته و مقداردهی رجیسترهای کنترلی استفاده می‌کند.

در مقابل رابط کنترلی SWD، خط ردگیری Serial Wire Output (کوته‌نوشت: SWO) روی پین PB3 (پیکربندی در مد عملکرد جایگزین TRACE_SWO/JTDO) قرار دارد. رابط SWO یک کانال ارسال دادهٔ یک‌طرفه (Unidirectional) از پردازنده به سمت پروب دیباگر است که پیام‌های ماژول ردیابی ابزار دقیق (Instrumentation Trace Macrocell یا ITM) را بدون نیاز به اشغال واحدهای جانبی نظیر USART منتقل می‌کند. پروب دیباگر باید حتماً از خط سخت‌افزاری SWO پشتیبانی کرده و پین مربوطه را به درگاه متصل کند؛ وجود SWD روی یک دانگل به‌تنهایی پشتیبانی و در دسترس بودن SWO را تضمین نمی‌کند؛ مستندات همان مدل را بررسی کنید.

در این پروژه، کلاک سیستم (SYSCLK) و کلاک گذرگاه هسته (HCLK) بدون راه‌اندازی واحد ضرب‌کنندهٔ فرکانس (PLL) مستقیماً روی اسیلاتور داخلی HSI با فرکانس نامی 8 MHz پیکربندی شده‌اند و کریستال‌های خارجی هنوز غیرفعال هستند. بنابراین در تنظیمات ردیابی ابزار توسعه، مقدار Core Clock باید دقیقاً برابر 8MHz (و نه 72MHz) وارد شود تا نرخ ارسال باودریت پین SWO با فرکانس قرائت پروب همگام بماند.

مسیرهای کنترل SWD و خروجی یک‌طرفهٔ SWO از PB3
نمودار آموزشی تألیفی؛ SWD برای کنترل دیباگ و SWO برای خروجی trace است. پشتیبانی و کانکتور پروب را از راهنمای همان مدل بررسی کنید. برای دیدن جزئیات، تصویر را باز کنید.

مدیریت سشن دیباگ و گام‌برداری در STM32CubeIDE

بستهٔ همراه، پروژهٔ CMake تولیدشده با CubeMX است. برای دنبال‌کردن مراحل گرافیکی، ابتدا از .ioc در پوشه‌ای جدا کپی بگیرید، خروجی STM32CubeIDE تولید و همان بخش‌های USER CODE مقاله را در محل متناظر منتقل کنید؛ توابع تولیدشده را دوباره تعریف نکنید. پروژه را با اطلاعات دیباگ بسازید و در Debug Configurations، پیکربندی STM32 C/C++ Application، فایل ELF همان ساخت و اتصال ST-LINK با SWD را انتخاب کنید. گزینه‌های دانلود و Run to main را بررسی کنید تا نشست پس از راه‌اندازی در main متوقف شود. نام منوها می‌تواند با نسخه فرق کند. مراحل زیر راهنمای انجام کارند؛ نشست واقعی CubeIDE در تهیهٔ مقاله اجرا نشده است.

  • Resume (F8): ادامهٔ اجرای عادی پردازنده با تمام سرعت تا رسیدن به یک شرط توقف دیباگر؛ وقفهٔ عادی به‌تنهایی پردازنده را در دیباگر متوقف نمی‌کند.
  • Suspend: توقف اجباری و فوری اجرای پردازنده و ثبت موقعیت کنونی شمارندهٔ برنامه (PC).
  • Step Into (F5): پرش به اولین دستور درون یک تابع فراخوانی‌شده برای بررسی دقیق جزییات منطقی آن.
  • Step Over (F6): اجرای گام جاری در سطح سورس؛ اگر خط شامل فراخوانی تابع باشد، معمولاً تا بازگشت آن پیش می‌رود؛ این دستور تابع را نادیده نمی‌گیرد، بلکه آن را تا پایان اجرا می‌کند.
  • Step Return (F7): ادامهٔ اجرای سریع تابع جاری تا خروج از آن و توقف در محل ادامهٔ تابع فراخواننده، با توجه به اطلاعات دیباگ.
  • Reset و Terminate: عملگر ریست وضعیت پردازنده را به حالت اولیه بازمی‌گرداند؛ دستور قطع اتصال (Terminate) سشن دیباگ را پایان می‌دهد، اما وضعیت ادامه یا توقف تراشه بسته به تنظیمات اتصال پروب متغیر خواهد بود.

واحد سخت‌افزاری FPB (Flash Patch and Breakpoint) در هستهٔ Cortex-M4 تعداد محدودی نقطهٔ توقف سخت‌افزاری فراهم می‌کند. به همین دلیل، نباید تعداد نامحدودی Breakpoint فعال در کدهای ذخیره‌شده در حافظهٔ فلش تعریف کرد.

تحلیل باگ منطقی پروژه و استراتژی Breakpoint شرطی

مشخصات کامپایل پروژه و تفکیک فضای حافظه در نسخهٔ دارای خطا و نسخهٔ اصلاح‌شده طبق داده‌های فنی زیر مستند شده است:

ساخت واقعی گزینه‌ها Flash، بایت RAM ناحیهٔ لینک، بایت
دارای باگ Debug -O0 -g3 8284 1600
دارای باگ RelWithDebInfo -Og -g3 4408 1600
اصلاح‌شده Debug -O0 -g3 8284 1600

گزینهٔ -Og برای RelWithDebInfo همین پروژه صریحاً تنظیم شده است؛ از نام پیکربندی، گزینه‌های همهٔ ابزارها را حدس نزنید. RAM گزارش‌شده شامل رزرو حداقل heap و stack است، نه مصرف واقعی اجرا. ELF و MAP هر 3 ساخت همراه‌اند. inspect_elf.py وجود نمادهای 4 متغیر و بخش‌های .debug_info و .debug_line را در ELFها تأیید کرده؛ گزارش آن آفلاین است و محل متغیرهای بهینه‌شده را از DWARF رمزگشایی نمی‌کند. فایل‌های متنی موقت objdump سرور در بسته باقی نمانده‌اند؛ فرمان بازتولید در راهنما آمده است. شناسهٔ CubeF3: 663d2baae3604244f90ed80d0d757df17e36ada9.

منطق پروژه بر مبنای یک حلقهٔ نامحدود طراحی شده است که وظیفه دارد به ازای هر 10 بار تکرار حلقه، دقیقاً 1 بار وضعیت LED کاربر (متصل به پین PB6 با درایور فعال‌ساز Active-High از طریق ترانزیستور NPN) را تغییر داده و برای فرستادن یک بایت گزارش تلاش کند. کدهای ثبت‌شده در بخش کدهای کاربر پروژه به شرح زیر هستند:

بلوک USER CODE 3 در خروجی این پروژه شامل آکولاد پایانی حلقه است؛ آن را دوباره به حلقهٔ موجود اضافه نکنید. نسخهٔ دارای باگ مقدار پیش‌فرض ماکرو 0 و پروژهٔ پاسخ مقدار 1 دارد.

بلوک USER CODE PD

/* USER CODE BEGIN PD */
#ifndef LESSON7_FIXED
#define LESSON7_FIXED 0
#endif
/* USER CODE END PD */

بلوک USER CODE PV

/* USER CODE BEGIN PV */
volatile uint32_t step_count = 0U;
volatile uint32_t event_count = 0U;
volatile uint32_t observed_value = 0U;
volatile uint32_t trace_dropped = 0U;
/* USER CODE END PV */

بلوک USER CODE PFP

/* USER CODE BEGIN PFP */
static void Trace_TryPutChar(uint8_t value);
/* USER CODE END PFP */

بلوک USER CODE 3

/* USER CODE BEGIN 3 */
    step_count++;
    uint32_t derived = step_count * 2U;
    observed_value = derived;
#if LESSON7_FIXED
    if (step_count >= 10U)
#else
    if (step_count > 10U)
#endif
    {
      event_count++;
      step_count = 0U;
      HAL_GPIO_TogglePin(USER_LED_GPIO_Port, USER_LED_Pin);
      Trace_TryPutChar((uint8_t)'E');
    }
    HAL_Delay(100U);
  }
  /* USER CODE END 3 */

در پیاده‌سازی دارای باگ، متغیر step_count در ابتدای حلقه افزایش می‌یابد (step_count++)، اما شرط اجرای رخداد به صورت if (step_count > 10U) تعریف شده است. این شرط زمانی برقرار می‌شود که شمارنده به مقدار 11 برسد؛ بنابراین دورهٔ تغییر وضعیت، 11 تکرار خواهد بود نه 10 تکرار. در پیاده‌سازی تصحیح‌شده، این شرط به if (step_count >= 10U) اصلاح می‌شود.

ابتدا پروژهٔ دارای باگ را با LESSON7_FIXED=0 و تنظیم Debug اجرا کنید. Breakpoint ساده را روی خط if (step_count > 10U)، پس از افزایش شمارنده و محاسبهٔ observed_value قرار دهید. با Resume رسیدن به همان خط را بررسی کنید. سپس در Breakpoint Properties شرط step_count == 10 را اضافه کنید. در این توقف، مقدار مورد انتظار step_count برابر 10 و observed_value برابر 20 است؛ با Step Over باید مسیر بدون رخداد انتخاب شود. برای مشاهدهٔ دور 11، شرط Breakpoint را به step_count == 11 تغییر دهید یا از توقف دور 10 با گام‌برداری ادامه دهید؛ Resume با شرط قبلی در 11 متوقف نمی‌شود. مقدار مورد انتظار observed_value در 11 برابر 22 است و ورود به بدنه، event_count را زیاد و step_count را صفر می‌کند. این مقادیر از تحلیل کد و اسکریپت‌اند، نه ثبت واقعی دیباگر.

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

پنجره‌های مشاهدهٔ متغیرها، رجیسترها و حافظه

دیباگر چندین رابط مجزا برای ارزیابی وضعیت اجرایی پردازنده در اختیارتان قرار می‌دهد:

  • Variables: متغیرهای محلی (Local) متناظر با قاب فراخوانی جاری (Stack Frame) را نشان می‌دهد. اگر یک متغیر محلی در بهینه‌سازی حذف شود، وضعیت آن به صورت <optimized out> درج خواهد شد.
  • Expressions: امکان رصد متغیرهای سراسری (Global)، ساختارهای داده‌ای و ارزیابی عبارات منطقی را فراهم می‌کند. متغیرهای مورد پایش مانند observed_value (که حاصل‌ضرب شمارنده در 2 است) به ترتیب پس از محاسبه در دور 10 برابر 20 و در دور 11 برابر 22 انتظار می‌روند.
  • Live Expressions: با تکیه بر درگاه دسترسی به حافظهٔ دیباگر (DAP)، مقادیر متغیرها را بدون متوقف‌کردن کامل اجرای هسته بازخوانی می‌کند. آن را برای متغیرهای سراسری یا static با آدرس معتبر مانند step_count امتحان کنید. پشتیبانی به پروب و پیکربندی وابسته است و به‌روزرسانی لحظه‌ای، هم‌زمان یا بدون سربار تضمین نمی‌شود؛ متغیر محلی derived الزاماً مناسب این پنجره نیست.
  • CPU Registers و Memory View: مشاهدهٔ مستقیم رجیسترهای اصلی پردازنده نظیر شمارندهٔ برنامه (PC)، اشاره‌گر پشته (SP) و وضعیت برنامه (xPSR) و همچنین دسترسی به آدرس‌های معتبر حافظه از روی جدول Linker Map.
  • Peripheral SFRs: امکان بررسی رجیسترهای جانبی را مهیا می‌سازد. در این درس، خواندن رجیسترهای امن نظیر GPIOB->ODR یا رجیستر پیکربندی کلاک RCC->CFGR یا مقدار بارگذاری مجدد تایمر سیستمی SysTick->LOAD (با مقدار مورد انتظار 7999 برای ایجاد وقفهٔ 1 میلی‌ثانیه با کلاک 8MHz) توصیه می‌شود. توجه شود که خواندن تصادفی رجیسترهای سخت‌افزاری که با عمل خواندن پاک می‌شوند (مانند FIFOها و رجیسترهای وضعیت با رفتار Read-to-Clear)، عملکرد محیط جانبی را مختل می‌کند.

چهار متغیر سراسری این تمرین volatile هستند تا دسترسی‌های لازم آن‌ها در ماشین مطابق الزامات زبان حفظ شود؛ volatile تضمین قرارگیری همیشگی هر متغیر در SRAM یا نمایش آن در هر نقطهٔ دیباگ نیست. این ویژگی به‌تنهایی دسترسی اتمیک، همگام‌سازی یا ایمنی هم‌زمانی ایجاد نمی‌کند. متغیر محلی derived برای مقایسهٔ رفتار بهینه‌سازی نگه داشته شده است.

ارسال ردیابی با ITM/SWO بدون مسدودسازی برنامه

واحد ردیابی ابزار دقیق (ITM) امکان انتقال بایت‌های تشخیصی را از پین PB3 به دیباگر میسر می‌کند. ارسال این بایت‌ها سربار زمانی اندکی روی پردازنده دارد و در صورت عدم پیکربندی صحیح، پر شدن بافر یا عدم اتصال پین SWO در پروب، برنامه نباید در حلقهٔ انتظار قفل شود. تابع کمکی غیرمسدودکنندهٔ Trace_TryPutChar ابتدا فعال‌بودن سیستم ردیابی در واحد CoreDebug، رجیستر کنترلی ITM و مجاز بودن پورت 0 را اعتبارسنجی می‌کند:

بلوک USER CODE 4

/* USER CODE BEGIN 4 */
static void Trace_TryPutChar(uint8_t value)
{
  if (((CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk) != 0U) &&
      ((ITM->TCR & ITM_TCR_ITMENA_Msk) != 0U) &&
      ((ITM->TER & 1UL) != 0U) &&
      (ITM->PORT[0].u32 != 0U))
  {
    ITM->PORT[0].u8 = value;
  }
  else
  {
    trace_dropped++;
  }
}
/* USER CODE END 4 */

افزایش شمارندهٔ trace_dropped لزوماً به مفهوم قطعی فیزیکی سیم نیست؛ بلکه می‌تواند ناشی از پر بودن بافر پورت خروجی، خاموش بودن نمایشگر ردیابی در محیط نرم‌افزار، یا عدم فعال‌سازی رجیسترهای ردیابی توسط دیباگر باشد. حتی در صورت عدم دسترسی به قابلیت SWO، روند دیباگ منطقی با استفاده از Breakpointها و پایش رجیستر وضعیت LED همچنان کامل و مستقل پیش می‌رود.

راه‌اندازی اختیاری نمایش SWV و محدودیت توقف

اگر پروب SWO دارد، با تغذیهٔ قطع، PB3 را مطابق راهنمای همان پروب به ورودی SWO وصل کنید و GND مشترک را برقرار نگه دارید. SWD همان اتصال درس 6 است؛ پایهٔ مرجع ولتاژ هدف را خروجی تغذیه فرض نکنید. در نشست Debug، تنظیم trace/SWV را فعال، Core Clock را 8 MHz و فرکانس SWO را مقدار پشتیبانی‌شدهٔ پروب انتخاب کنید. در SWV ITM Data Console پورت 0 را فعال و دریافت را شروع کنید، سپس Resume کنید. نام پنجره‌ها وابسته به نسخه است. دیباگر باید مسیر trace شامل TPIU و ITM را پیکربندی کند؛ تابع کاربر تنها آمادگی ارسال را می‌سنجد و خودش این مسیر را فعال نمی‌کند. در رخداد، نویسهٔ E انتظار می‌رود؛ نبودن آن به‌تنهایی خرابی برنامه را ثابت نمی‌کند.

خواندن رجیسترها را به آدرس‌های معتبر و بدون عارضهٔ جانبی محدود کنید؛ در این تمرین در PC، SP، xPSR یا رجیسترهای محیط جانبی مقدار دلخواه ننویسید. توقف در ISR زمان‌بندی برنامه را تغییر می‌دهد و روش سنجش تأخیر وقفه نیست. هنگام Step Into در HAL_Delay، پیشروی به تیک و وضعیت وقفه‌ها وابسته است؛ برای ادامهٔ عادی از Resume استفاده کنید. مقایسهٔ -O0 و -Og را با ساخت مجدد و ELF متناظر انجام دهید.

اصلاح و ثبت نتیجهٔ آزمایش

ابتدا فرضیه و مقادیر مورد انتظار را در debug-worksheet.csv ثبت کنید. بدون استفاده از printf یا تغییر تصادفی حافظه، از Breakpoint و Expressions برای تشخیص شرط نادرست استفاده کنید. سپس سورس را به شرط >= 10U اصلاح، دوباره بسازید و فایل ELF جدید را بارگذاری کنید؛ تغییر سورس به‌تنهایی برنامهٔ داخل برد را عوض نمی‌کند. پروژهٔ fixed همراه بسته پاسخ قابل مقایسه است. بررسی کنید رخداد نخست در دور 10 و observed_value برابر 20 انتظار می‌رود. verify_bug.py این پیش‌بینی را بازتولید می‌کند؛ ستون مشاهدهٔ واقعی کاربرگ تا انجام آزمایش خالی می‌ماند.

تأثیر سطوح بهینه‌سازی (-O0 در برابر -Og)

تفاوت سطح بهینه‌سازی تأثیر محسوسی بر رفتار دیباگر دارد:

پارامتر مقایسه پیکربندی Debug (-O0 -g3) پیکربندی RelWithDebInfo (-Og -g3)
هدف بهینه‌سازی غیرفعال‌کردن بیشتر بهینه‌سازی‌ها برای ساده‌تر شدن بررسی بهینه‌سازی پایه با اولویت حفظ قابلیت دیباگ
حجم حافظهٔ Flash پروژه 8284 بایت 4408 بایت
وضعیت متغیرهای محلی معمولاً ردیابی ساده‌تر است؛ نمایش همهٔ متغیرها و قرارگیری همه در پشته تضمین نمی‌شود برخی متغیرهای میانی ممکن است <optimized out> شوند
ترتیب اجرای دستورات انطباق سورس و دستور ماشین معمولاً ساده‌تر است؛ هر خط الزاماً یک دستور نیست امکان جابه‌جایی جزئی ترتیب دستورات توسط کامپایلر

جدول منابع اولیه و مستندات فنی

برای بررسی استانداردهای معماری و جزئیات ثبات‌ها، مستندات رسمی سازنده مرجع اصلی هستند:

عنوان مرجع فنی کد شناسایی / ناشر لینک دسترسی مستقیم
راهنمای کاربری محیط نرم‌افزاری STM32CubeIDE STMicroelectronics UM2609 دانلود سند UM2609
راهنمای کاربری دیباگر سخت‌افزاری ST-LINK/V2 STMicroelectronics UM1075 دانلود سند UM1075
مرجع کامل خانوادهٔ میکروکنترلرهای STM32F303 STMicroelectronics RM0316 دانلود سند RM0316
دیتاشیت تراشهٔ میکروکنترلر STM32F303CB STMicroelectronics DS9118 دانلود سند DS9118
شماتیک سخت‌افزاری برد توسعهٔ GebraBit STM32F303 V2.0 GebraBit GB100STV2 دانلود شماتیک برد
تعاریف رجیسترهای هسته در درایور CMSIS Core CM4 ARM / ST CubeF3 Driver مخزن کدهای CMSIS Include
راهنمای سوئیچ‌های دیباگ کامپایلر GCC GNU Compiler Collection مستندات GCC Debugging

خودآزمایی و چک‌لیست آزمایشگاهی دیباگ

پیش از ورود به مراحل عملی درس بعدی، پاسخ به سؤالات زیر درک مفاهیم کلیدی را تثبیت می‌کند:

  1. چرا شرط > 10U باعث 11 بار تکرار می‌شود؟ زیرا متغیر از 1 تا 10 برای این عبارت نامعتبر (False) ارزیابی شده و نخستین بار در تکرار 11 شرط برقرار می‌شود؛ بنابراین شرط صحیح برای دوره‌های 10 عددی >= 10U است.
  2. تفاوت رفتاری کلید Step Over با نادیده‌گرفتن اجرای تابع چیست؟ کلید Step Over کدهای تابع را با سرعت نامی سیستم تا انتها اجرا می‌کند و فقط از ردیابی خط‌به‌خط آن عبور کرده و در دستور بعدی متوقف می‌شود.
  3. چرا خط ارتباطی SWO جایگزین درگاه UART محسوب نمی‌شود؟ زیرا SWO یک مسیر ردیابی یک‌طرفه سخت‌افزاری برای گزارش‌گیری پروب دیباگر است و قابلیت تعامل دوطرفه با تجهیزات جانبی مدار را ندارد.
  4. آیا عبارت <optimized out> در پنجرهٔ متغیرها نشان‌دهندهٔ خطا در کد است؟ خیر، این وضعیت نشان می‌دهد که کامپایلر به منظور بهبود سرعت یا اندازهٔ کد، نیازی به تخصیص متغیر در رم ندیده و ممکن است متغیر حذف، ادغام یا جابه‌جا شده باشد، یا مکان معتبر آن برای نقطهٔ جاری در اطلاعات دیباگ در دسترس نباشد.
مرحلهٔ آزمایش پیش‌بینی نسخهٔ دارای باگ؛ مشاهده نشده اقدام اصلاحی در دیباگر وضعیت هدف پس از اصلاح
شرط مقایسهٔ شمارنده step_count > 10U تغییر شرط منطقی با ارزیابی متغیرها step_count >= 10U
تعداد تکرار در هر تریگر LED 11 گام تکرار حلقه پایش مقدار متغیر در Breakpoint شرطی 10 گام تکرار حلقه
مقدار متغیر observed_value مقدار 22 در هنگام تحریک شرط بررسی مقدار در پنجرهٔ Expressions مقدار 20 در هنگام تحریک شرط

در درس آینده (درس 8)، کنترل مستقیم پایه‌ها و رجیسترهای واحد ورودی/خروجی همه‌منظوره (GPIO) بدون توقف پردازنده پیاده‌سازی خواهد شد؛ فرکانس کاری نهایی سیستم (72MHz با PLL) در درس 9 تنظیم می‌گردد.

دانلود فایل‌های همراه این آموزش

دو پروژهٔ کامل دارای باگ و اصلاح‌شده، خروجی ELF و MAP برای 3 ساخت واقعی بدون هشدار، گزارش‌های ساخت و تحلیل آفلاین ELF، اسکریپت پیش‌بینی خطا، کاربرگ ثبت مشاهده و 2 نمودار آموزشی. نشست CubeIDE، پروگرام برد و دریافت واقعی SWO انجام نشده‌اند.

دانلود به نسخهٔ مشخص همراه مقاله متصل است. ابتدا ZIP را استخراج و راهنما را مطالعه کنید.

با نظرات خود به تیم جبرا در بهبود کیفیت کمک کنید

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

سبد خرید
پیمایش به بالا