فرض کنید می‌خواهید نور یک LED را کم و زیاد کنید. در درس GPIO خروجی را High یا Low می‌کردیم. حالا به‌جای تغییر سطح High، مدت روشن‌ماندن را تغییر می‌دهیم: مثلاً در هر 1000 میکروثانیه، LED فقط 250 میکروثانیه روشن باشد. اگر این کار سریع تکرار شود، می‌توان روشنایی دیده‌شده را تنظیم کرد. برای این مثال، LED خارجی و مدار مناسب لازم است؛ LED خود برد به خروجی انتخاب‌شدهٔ این درس وصل نیست.

این روش مدولاسیون پهنای پالس یا PWM نام دارد؛ مخفف Pulse Width Modulation است. در این درس، تایمر 3 موج را روی PA6 می‌سازد. ابتدا شکل موج و محاسباتش را می‌شناسیم، سپس تنظیمات و کد را دنبال می‌کنیم. پیش‌نیازها: درس 16، ساختمان تایمر و درس 18، وقفهٔ تایمر. برای خود PWM این پروژه به وقفهٔ تایمر نیاز نداریم.

چطور با قطع و وصل سریع، ولتاژ متوسط بسازیم؟

برای درک سازوکار PWM، ابتدا باید یک شکل‌موج پالسی متناوب را توصیف کنیم. هر موج پالسی در یک بازه زمانی تکرار می‌شود که به آن دوره تناوب (Period) می‌گوییم و با نماد T بر حسب ثانیه نمایش می‌دهیم. تعداد تکرار این چرخه در هر ثانیه، فرکانس (Frequency) سیگنال است که با نماد fPWM و با واحد هرتز (Hz) اندازه گرفته می‌شود. رابطه بین دوره و فرکانس همواره معکوس یکدیگر است:

T = 1 / fPWM

در طول هر دوره تناوب، سیگنال برای مدتی در سطح منطقی بالا (High یا 3.3 ولت) قرار دارد که آن را با TON نشان می‌دهیم، و در باقی‌مانده دوره در سطح منطقی پایین (Low یا 0 ولت) می‌ماند که TOFF نامیده می‌شود. حاصل‌جمع این دو زمان برابر با کل دوره تناوب است (T = TON + TOFF). نسبت مدت‌زمان روشن بودن به کل دوره تناوب، نسبت وظیفه (Duty Cycle) نام دارد و با نماد D مشخص می‌شود:

D = TON / T

نسبت وظیفه یک کسر بدون بعد بین 0 و 1 است که معمولاً آن را به صورت درصد بیان می‌کنیم (D% = 100 × D). اگر در یک دوره زمانی 1000 میکروثانیه‌ای، سیگنال به مدت 250 میکروثانیه روشن و 750 میکروثانیه خاموش باشد، نسبت وظیفه برابر 0.25 یا 25% خواهد بود.

تفاوت کلیدزنی سریع با ولتاژ آنالوگ واقعی

PWM روی پایهٔ دیجیتال، ولتاژ آنالوگ ثابت 1.65V تولید نمی‌کند؛ پایه میان دو سطح High و Low جابه‌جا می‌شود. برای محاسبهٔ ساده، آن‌ها را 3.3V و 0V فرض می‌کنیم. در مدار واقعی، مقدار دقیق سطح‌ها و زمان لبه‌ها به تغذیه، بار و مشخصات پایه وابسته‌اند. میانگین یک دوره از رابطهٔ زیر به دست می‌آید؛ D در این رابطه کسر زمانی سطح High است.

Vavg = (D × VHigh) + ((1 – D) × VLow)

با فرض ایده‌آل VHigh = 3.3 V و VLow = 0 V، این رابطه به فرم ساده Vavg = D × 3.3 V خلاصه می‌شود. به عنوان مثال، برای نسبت‌های وظیفه 25%، 50% و 75%، ولتاژ میانگین تئوری به ترتیب برابر 0.825 ولت، 1.650 ولت و 2.475 ولت خواهد بود.

اثر PWM به مدار بستگی دارد. فیلتر مقاومت و خازن می‌تواند نوسان را کاهش دهد، ولی خروجی آن ریپل و زمان نشست دارد و یک ولتاژ کاملاً صاف را تضمین نمی‌کند. LED همچنان قطع و وصل می‌شود؛ دیده‌شدن چشمک به فرکانس و شرایط مشاهده وابسته است و تغییر روشناییِ دیده‌شده لزوماً با Duty خطی نیست. موتور نیز به درایور و مدار حفاظت مناسب نیاز دارد و سرعتش صرفاً از ولتاژ میانگین به دست نمی‌آید. موتور را مستقیم به پایهٔ MCU وصل نکنید.

مقایسهٔ زمان روشن و خاموش PWM با دامنهٔ ثابت
مدل تألیفی ایدئال PWM با دورهٔ 1000us و سطح‌های 0V و 3.3V؛ زمان‌های روشن و خاموش برای 25% و 50% و 75% و میانگین‌های 0.825V و 1.65V و 2.475V نشان داده شده‌اند. خط میانگین به معنی خروجی DC تنظیم‌شده یا ولتاژ اندازه‌گیری‌شدهٔ بار نیست. برای دیدن جزئیات، تصویر را باز کنید.

چرا خود تایمر پالس را بسازد؟

در درس 18 پایه با اجرای callback معکوس می‌شد. اگر لبه‌های موج را نرم‌افزار بسازد، CPU باید به‌موقع کد اجرا کند؛ وقفه‌ها یا تأخیر کارهای دیگر می‌توانند زمان لبه‌ها را جابه‌جا کنند. در PWM سخت‌افزاری، پس از تنظیم و شروع، تایمر شمارش و مقایسه را انجام می‌دهد؛ لازم نیست CPU برای هر لبه وارد ISR شود. تنظیم و تغییر پارامترها هنوز هزینه دارد و دقت موج به کلاک و سخت‌افزار وابسته است؛ درصد بار CPU این پروژه اندازه‌گیری نشده است.

تایمر چگونه شکل‌موج خروجی را در سخت‌افزار می‌سازد؟

برای پیاده‌سازی PWM در سخت‌افزار، تایمر از ترکیب دو بخش اصلی بهره می‌برد: یک «شمارنده صعودی» و یک «واحد مقایسه‌گر دیجیتال». همان‌طور که در درس 16: تایمرهای عمومی STM32 شرح داده شد، رجیستر شمارنده (CNT سرنام Counter) با هر پالس دریافتی از مقسم کلاک (PSC سرنام Prescaler)، یک واحد افزایش می‌یابد. سقف این شمارش توسط رجیستر بازنشانی خودکار (ARR سرنام Auto-Reload Register) تعیین می‌شود.

در کنار شمارنده، رجیستر مقایسه یا CCR قرار دارد؛ Capture/Compare Register. رجیستر محل نگهداری یک مقدار در سخت‌افزار است. برای کانال 1 از CCR1 استفاده می‌کنیم. در شمارش صعودی با PWM Mode 1 و قطبیت High و کانال فعال، وقتی CNT کوچک‌تر از CCR1 باشد، خروجی High است؛ وقتی CNT به CCR1 برسد یا از آن بیشتر شود، خروجی Low است. مثلاً با CCR1=250، نخستین 250 تیک هر دوره High می‌شوند.

با ARR=999، شمارنده از 0 تا 999 می‌رود و در تیک بعد به 0 برمی‌گردد؛ یعنی هر دوره 1000 تیک دارد. در تنظیم عادی این پروژه، پایان دور رخداد به‌روزرسانی یا UEV می‌سازد؛ Update Event. در مثال CCR1=250، خروجی در ابتدای دورهٔ بعد دوباره High می‌شود. پس ARR طول دوره و CCR1 طول بخش High را تعیین می‌کنند. حالت CCR1=0 را جدا در بخش مرزها بررسی می‌کنیم.

نمونه‌های گسستهٔ شمارنده و مقایسه با CCR1 برابر 250
مدل تألیفی شمارش صعودی، PWM Mode1 و قطبیت High با ARR=999 و CCR1=250؛ خروجی برای CNT از 0 تا 249 در سطح High و از 250 به بعد Low است. نقطه‌ها فقط نمونه‌های منتخب‌اند؛ شمارنده پله‌های تک‌تیک دارد و رمپ پیوستهٔ آنالوگ نیست. برای دیدن جزئیات، تصویر را باز کنید.

مسیر فرکانس کلاک ورودی تایمر 3 در STM32F303

در پروژهٔ همراه، کریستال HSE اسمی 8MHz با PREDIV برابر 1 و PLL برابر 9، کلاک SYSCLK/HCLK اسمی 72MHz می‌سازد. APB1 با تقسیم 2 برابر 36MHz و APB2 با تقسیم 1 برابر 72MHz است؛ Flash Latency روی 2WS قرار دارد. TIM3 به APB1 وصل است، اما برای محاسبهٔ PWM نباید مستقیماً 36MHz را به‌جای کلاک تایمر بگذاریم.

در مسیر کلاک TIM3 این تراشه، اگر مقسم APB1 برابر 1 باشد کلاک تایمر همان PCLK1 است؛ اگر مقسم غیر از 1 باشد دو برابر PCLK1 می‌شود. مقسم این پروژه 2 است؛ بنابراین:

fTIM = 36 MHz × 2 = 72 MHz

آگاهی از این کلاک مبنای 72 مگاهرتزی، پایه تمامی محاسبات دقیق فرکانس و نسبت وظیفه در ادامه این درس است.

چگونه فرکانس و تفکیک‌پذیری PWM را محاسبه کنیم؟

تمامی روابط این بخش به طور صریح مشروط به برقراری سه وضعیت کاری مشخص هستند: شمارش صعودی (Upcounting)، مد خروجی PWM Mode 1 و قطبیت فعال بالا (Active-High) روی کانال فعال خروجی. در این شرایط، شمارنده از 0 تا ARR را می‌شمارد؛ بنابراین تعداد کل کوانتاهای زمانی یا پله‌های گسسته در هر دوره تناوب برابر با N = ARR + 1 است.

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

fPWM = fTIM / ((PSC + 1) × (ARR + 1))

نسبت وظیفه کسری (D) و پهنای پالس خروجی (TON) برای هر مقدار عددی قابل‌بیان در محدوده 0 ≤ CCR ≤ ARR + 1 از رابطه زیر پیروی می‌کنند:

D = min(CCR / (ARR + 1), 1)
TON = D × T = (CCR / (ARR + 1)) × (1 / fPWM)

نمونه محاسباتی: تولید سیگنال 1 کیلوهرتز

هدف، فرکانس اسمی 1kHz یا 1000Hz است. با کلاک تایمر 72MHz، PSC=71 را انتخاب می‌کنیم؛ تقسیم بر PSC+1 یعنی 72، نرخ شمارش را به 1MHz و زمان هر تیک را به 1us می‌رساند. اگر ARR=999 باشد، 1000 تیک در هر دوره خواهیم داشت. حالا مقدارها را در فرمول قرار می‌دهیم:

fPWM = 72,000,000 / ((71 + 1) × (999 + 1)) = 72,000,000 / (72 × 1000) = 1000 Hz = 1 kHz

کل دوره تناوب برابر T = 1 / 1000 = 1 ms = 1000 µs است. اکنون مقادیر CCR را برای سه نسبت وظیفه پرکاربرد بررسی می‌کنیم:

  • نسبت وظیفه 25%: با انتخاب CCR = 250، پهنای پالس برابر TON = (250 / 1000) × 1000 µs = 250 µs و ولتاژ میانگین ایده‌آل برابر Vavg = 0.25 × 3.3 V = 0.825 V خواهد بود.
  • نسبت وظیفه 50%: با انتخاب CCR = 500، پهنای پالس برابر TON = 500 µs و ولتاژ میانگین ایده‌آل برابر Vavg = 0.50 × 3.3 V = 1.650 V است.
  • نسبت وظیفه 75%: با انتخاب CCR = 750، پهنای پالس برابر TON = 750 µs و ولتاژ میانگین ایده‌آل برابر Vavg = 0.75 × 3.3 V = 2.475 V می‌شود.

مفهوم تفکیک‌پذیری و مصالحه مهندسی فرکانس و دقت

تفکیک‌پذیری (Resolution) تعیین می‌کند که کوچک‌ترین گام تغییر نسبت وظیفه چقدر است. در مثال فوق با N = 1000 پله زمانی، حداقل تغییر ممکن در نسبت وظیفه با افزایش یک واحدی در رجیستر مقایسه برابر است با ΔD = 1 / 1000 = 0.1% (0.1 واحد درصد). از دیدگاه معادل بیتی، تفکیک‌پذیری زمانی سیستم برابر log2(1000) ≈ 9.9658 بیت است. باید توجه داشت که این مقدار صرفاً یک شاخص محاسباتی گسسته‌سازی زمانی است و نباید آن را با «تعداد بیت‌های موثر» (ENOB) در مبدل‌های داده اشتباه گرفت.

حال این پرسش مطرح می‌شود: اگر بخواهیم همان فرکانس 1 کیلوهرتز را با بیشترین تفکیک‌پذیری ممکن بسازیم، آیا می‌توانیم PSC = 0 را انتخاب کنیم؟ پاسخ منفی است. بیایید فرمول را معکوس کنیم: اگر PSC = 0 باشد، مخرج کسر باید 72000 باشد (ARR + 1 = 72,000,000 / 1000 = 72000)، یعنی باید مقدار ARR = 71999 درون رجیستر قرار گیرد. اما تایمر 3 یک تایمر 16 بیتی است! حداکثر عددی که یک رجیستر 16 بیتی بدون علامت می‌تواند در خود ذخیره کند 216 – 1 = 65535 است. عدد 71999 در این رجیستر جا نمی‌شود؛ چنین تنظیمی معتبر نیست و نباید به نتیجهٔ نوشتن آن تکیه کرد.

بهترین انتخاب مهندسی برای رسیدن به فرکانس 1 کیلوهرتز با بالاترین تفکیک‌پذیری ممکن در تایمر 16 بیتی، انتخاب PSC = 1 است. در این حالت ARR + 1 = 72,000,000 / (2 × 1000) = 36000 می‌شود که نتیجه آن ARR = 35999 است. این مقدار کاملاً درون بازه 16 بیتی قرار می‌گیرد و به ما 36000 پله زمانی با گام 0.002778% معادل 15.1357 بیت تفکیک‌پذیری می‌دهد.

اگر هدف را به 20kHz تغییر دهیم، PSC=0 و ARR=3599 تعداد 3600 تیک در هر دوره می‌دهند؛ گام Duty حدود 0.02778 واحد درصد و معادل زمانی حدود 11.8138 بیت است. این یک محاسبه بر اساس کلاک اسمی است. انتخاب 20kHz به‌تنهایی تضمین حذف صدای هر مدار یا موتور نیست.

سه انتخاب گسستهٔ PSC و ARR با تفکیک‌پذیری متفاوت
نمودار محاسباتی تألیفی با کلاک اسمی 72MHz؛ دو تنظیم 1kHz با N برابر 1000 و 36000 و یک تنظیم 20kHz با N برابر 3600 مقایسه می‌شوند. نقطه‌ها به هم وصل نشده‌اند؛ نمودار مرز پیوسته یا رابطهٔ یکنواخت مستقل از PSC را ادعا نمی‌کند. N بیشینه 65536 است و log2(N) معادل شمارشی است، نه ENOB اندازه‌گیری‌شده. برای دیدن جزئیات، تصویر را باز کنید.

حالت‌های کاری، جهت شمارش و رفتارهای مرزی چه تفاوتی دارند؟

تا اینجا PWM Mode 1 و قطبیت High را به کار بردیم. برای مقایسه، Mode 2 منطق فعال‌بودن کانال را معکوس می‌کند؛ بیت CC1P نیز قطبیت خروجی را تعیین می‌کند. جدول زیر برای شمارش صعودی، کانال فعال و مقدار ثابت CCR است. در جدول، D همان کسر CCR/(ARR+1) در بازهٔ 0 تا 1 است؛ ستون آخر مشخص می‌کند پایه چه کسری از دوره واقعاً High می‌ماند. «فعال بودن بار» با High بودن پایه همیشه یک معنی ندارد.

مد کاری تایمر قطبیت (CC1P) وضعیت در زمان CNT < CCR وضعیت در زمان CNT ≥ CCR کسر زمانی High فیزیکی
PWM Mode 1 Active-High (0) High (3.3V) Low (0V) D
PWM Mode 1 Active-Low (1) Low (0V) High (3.3V) 1 – D
PWM Mode 2 Active-High (0) Low (0V) High (3.3V) 1 – D
PWM Mode 2 Active-Low (1) High (3.3V) Low (0V) D

همان‌طور که مشاهده می‌شود، تغییر مد از 1 به 2 یا معکوس کردن بیت قطبیت، کسر زمانی سطح High فیزیکی را از D به 1 – D تبدیل می‌کند.

چهار ترکیب PWM Mode و قطبیت CC1P
جدول شکل‌موج تألیفی برای شمارش صعودی، N=1000 و CCR1=250 و کانال فعال؛ سهم سطح فیزیکی High برای Mode1/CC1P0، Mode1/CC1P1، Mode2/CC1P0 و Mode2/CC1P1 به‌ترتیب 25% و 75% و 75% و 25% است. این مقایسهٔ منطقی ادعای آزمایش چهار پیکربندی روی برد نیست. برای دیدن جزئیات، تصویر را باز کنید.

شمارش لبه‌ای در برابر شمارش متقارن مرکزی

حالت فعلی، لبه‌تراز و صعودی است: شمارنده از 0 تا ARR می‌رود و دور بعد را از 0 شروع می‌کند. TIM3 حالت مرکزتراز هم دارد؛ در آن شمارنده تا ARR بالا و سپس تا 0 پایین می‌رود. برای ARR مثبت، یک رفت‌وبرگشت کامل 2×ARR تیک دارد. این بخش فقط معرفی است؛ پروژهٔ همراه مرکزتراز نیست و رفتار رخدادهای Update آن را نیز نباید با حالت صعودی یکی دانست.

دوره تناوب در شمارش متقارن مرکزی برای مقادیر مثبت ARR از رابطه زیر به دست می‌آید:

T = 2 × ARR × (PSC + 1) / fTIM

یک خطای متداول مهندسی، قرار دادن کورکورانه عبارت ARR + 1 در فرمول شمارش مرکزی است. توجه کنید که در شمارش مرکزی، ضریب دقیقاً 2 × ARR است و نباید با حالت لبه‌ای اشتباه شود.

تحلیل دقیق رفتارهای مرزی در مقادیر انتهایی

مرزهای زیر فقط برای شمارش صعودی، PWM Mode 1، قطبیت High و خروجی فعال‌اند. اگر مد یا قطبیت عوض شود، نتیجهٔ High/Low نیز عوض می‌شود:

  • حالت CCR = 0: شرط CNT < 0 برای یک شمارنده بدون علامت هرگز برقرار نمی‌شود؛ بنابراین خروجی در تمام طول دوره تناوب در سطح Low می‌ماند (D = 0%).
  • حالت CCR = ARR + 1: از آنجا که شمارنده تنها در محدوده 0 تا ARR حرکت می‌کند، شرط CNT < CCR همواره در تمام چرخه برقرار است و خروجی به طور دائمی High می‌ماند (D = 100%). با احتساب این نقطه انتهایی، تعداد سطوح گسسته خروجی برابر با N + 1 سطح خواهد بود.
  • حالت CCR > ARR + 1: خروجی کماکان 100% High باقی می‌ماند و نسبت وظیفه به بیش از 100% جهش نمی‌کند.
  • حالت خاص ARR = 65535: در این حالت دوره تناوب شامل N = 65536 پله است. از آنجا که رجیستر 16 بیتی CCR نمی‌تواند عدد 65536 را در خود نگه دارد و حداکثر مقدار آن 65535 است، حداکثر نسبت وظیفه تنظیمی برابر با 65535 / 65536 ≈ 99.9985% خواهد بود و همواره یک تک‌پالس بسیار کوتاه Low در انتهای چرخه باقی می‌ماند. برای دستیابی به 100% کامل در این فرکانس، باید از روش‌های مهندسی دیگر مانند تغییر موقت مد خروجی یا معکوس‌سازی قطبیت استفاده کرد. همچنین مقدار ARR = 0 شمارنده را در سطح سخت‌افزار مسدود می‌کند.

چرا رجیسترهای پیش‌بارگذاری از پرش ناخواسته پالس جلوگیری می‌کنند؟

فرض کنید ARR=999 و CCR1=750 است. اگر Preload خاموش باشد و در CNT نزدیک 500 مقدار CCR1 را به 250 تغییر دهیم، آستانهٔ مقایسه در میانهٔ همان دوره عوض می‌شود؛ پالس گذار می‌تواند با پهنای قدیم یا پهنای جدید ناسازگار شود. مدل آموزشی شکل، تغییر را دقیقاً در تیک 500 فرض می‌کند؛ کد Polling چنین لحظهٔ دقیقی را تضمین نمی‌کند. این فقط تصویرسازیِ منطق مقایسه‌گر است. مرجع RM0316 برای این تایمر، PWM بدون Preload را فقط در حالت تک‌پالس OPM معتبر می‌داند؛ در غیر این صورت رفتار را تضمین نمی‌کند. بنابراین در آزمایش PWM پیوسته با OC1PE خاموش، حتی پهنای 500 تیکِ این تصویرسازی هم نتیجهٔ تضمین‌شدهٔ سخت‌افزار نیست.

برای تغییر منظم Duty، از پیش‌بارگذاری یا Preload استفاده می‌کنیم. آن را مثل دو محل نگهداری عدد ببینید: یکی عدد بعدی را می‌گیرد و دیگری عدد فعلیِ مورد استفادهٔ مقایسه‌گر را نگه می‌دارد. با Preload فعال، نوشتن CCR1 مقدار فعلی را همان لحظه تغییر نمی‌دهد. در رخداد به‌روزرسانی مجاز، عدد بعدی به بخش فعال یا Shadow منتقل می‌شود. در تنظیم صعودی این پروژه، Update طبیعی در مرز دوره رخ می‌دهد؛ بنابراین تغییر CCR1 می‌تواند بدون دستکاری پالس جاری به دورهٔ بعد منتقل شود.

مسیر نوشتن پیش‌بارگذاری تا مقدار فعال تایمر
معماری تألیفی PSC و ARR و CCR1؛ با ARPE=1 و OC1PE=1، نوشتن پردازنده مقدار فعال را همان لحظه عوض نمی‌کند و انتقال با Update مجاز انجام می‌شود. PSC همواره بافر دارد. این شکل شرط‌های دقیق تایمر را حذف نمی‌کند و تضمین عمومی حذف همهٔ ناپایداری‌ها نیست. برای دیدن جزئیات، تصویر را باز کنید.

کنترل بیت‌های ARPE، OC1PE و بافر خودکار PSC

سازوکار بافر شدن در بخش‌های مختلف تایمر تفاوت‌هایی دارد که باید مد نظر قرار گیرد:

  • رجیستر PSC: همواره در سخت‌افزار بافر شده است و نیازی به فعال‌سازی بیتی ندارد. تغییر مقسم کلاک فقط در رخداد UEV اعمال می‌شود.
  • رجیستر ARR: با تنظیم بیت ARPE (سرنام Auto-Reload Preload Enable) در رجیستر TIMx_CR1 بافر می‌شود.
  • رجیستر CCR1: با تنظیم بیت OC1PE (سرنام Output Compare 1 Preload Enable) در رجیستر TIMx_CCMR1 بافر می‌گردد.

انتقال داده‌ها از رجیستر پیش‌بارگذاری به سایه، وابسته به رخداد طبیعی UEV در پایان شمارش است؛ مشروط بر آنکه بیت UDIS (غیرفعال‌ساز به‌روزرسانی) در رجیستر TIMx_CR1 روی صفر باشد.

خطرات تحریک دستی بیت UG در رجیستر رویدادها

میکروکنترلر دارای رجیستری به نام TIMx_EGR است که بیتی به نام UG (Update Generation) در آن وجود دارد. نوشتن عدد 1 در این بیت توسط نرم‌افزار، بلافاصله یک رخداد به‌روزرسانی مصنوعی تولید می‌کند. با این کار، رجیسترهای سایه فوراً به‌روز می‌شوند، اما هم‌زمان شمارنده CNT نیز به صفر ریست شده و فاز مقسم کلاک بازنشانی می‌گردد!

صدور UG هنگام اجرا می‌تواند شمارنده و فاز مقسم را دوباره آغاز کند؛ در نتیجه طول دوره یا یک بخش پالس تغییر می‌کند. اثر آن بسته به لحظهٔ اجرا و مقدارها ممکن است کوتاه یا بلندشدن یک بخش موج باشد؛ هر UG الزاماً یک پالس کوتاه یکسان نمی‌سازد. برای تغییر معمول CCR1 در این پروژه، OC1PE را فعال نگه دارید و منتظر Update طبیعی بمانید. در تابع بازگشت از Stop، UG زمانی صادر می‌شود که تایمر متوقف است تا مقدارهای بافر پیش از شروع بارگذاری شوند. نوشتن PSC، ARR و CCR سه دسترسی مستقل است؛ Preload به‌تنهایی آن‌ها را به یک تغییر اتمیک هم‌زمان تبدیل نمی‌کند.

مدل انتزاعی تغییر CCR و اثر مستقل UG بر فاز
مدل مقایسه‌گر تألیفی، نه پیش‌بینی قطعی برد: برای تصویرسازی، CCR از 750 به 250 در تیک ایدئال 500 تغییر می‌کند. شاخهٔ OC1PE=0 فقط مدل منطقی است؛ RM0316 استفاده از PWM بدون پیش‌بارگذاری را جز در One-pulse تضمین نمی‌کند و رفتار PWM پیوستهٔ آن تضمین‌شده نیست. با پیش‌بارگذاری، مدل دورهٔ قبلی را کامل می‌کند. شاخهٔ مستقل UG با CCR ثابت 250، شروع زودتر چرخه را نمایش می‌دهد؛ این شاخه حالت آزمایش کامپایل‌شده نیست و کد واقعی نوشتن را پس از CNT>=500 انجام می‌دهد. برای دیدن جزئیات، تصویر را باز کنید.

چگونه تایمر 3 را روی پایه PA6 برد GebraBit پیکربندی کنیم؟

در این پروژه از برد آموزشی GebraBit GB100ST V2.0 مجهز به میکروکنترلر STM32F303CBT6 در بسته‌بندی LQFP48 استفاده می‌کنیم. طبق برگه اطلاعات رسمی (Datasheet DS9118 Rev 14)، کانال 1 تایمر 3 (TIM3_CH1) از طریق نگاشت عملکرد متناوب شماره 2 (AF2) به پایه فیزیکی 16 یعنی PA6 هدایت می‌شود.

LED کاربر برد به PB6، پایهٔ فیزیکی 42 تراشه، وصل است؛ با PWM روی PA6 نور آن تغییر نمی‌کند. برای آزمایش اصلی، PA6 نام‌گذاری‌شدهٔ برد را به ورودی پرامپدانس اسیلوسکوپ وصل می‌کنیم. عدد 16 شمارهٔ پایهٔ بدنهٔ LQFP48 است، نه پین شمارهٔ 16 هدر؛ روی برد، برچسب PA6 را پیدا کنید. اگر برای تمرین LED خارجی اضافه می‌کنید، مقاومت سری و جریان مجاز را بر اساس LED و مشخصات خروجی انتخاب کنید؛ در بستهٔ این درس قطعهٔ LED خارجی مشخصی آزموده نشده است.

جدول نگاشت تنظیمات CubeMX به رجیسترهای میکروکنترلر

هنگام پیکربندی در محیط STM32CubeMX، گزینه‌های گرافیکی مستقیماً به رجیسترهای سخت‌افزاری زیر متصل می‌شوند:

پارامتر در محیط CubeMX مقدار تنظیمی برای پروژه رجیستر یا بیت سخت‌افزاری متناظر توضیح عملکرد مهندسی
Prescaler (PSC) 71 TIM3_PSC تقسیم کلاک مبنای 72MHz بر 72 برای رسیدن به فرکانس شمارش 1MHz
Counter Period (ARR) 999 TIM3_ARR تعیین سقف شمارش و ایجاد دوره تناوب 1000 میکروثانیه‌ای (1kHz)
auto-reload preload Enable بیت ARPE در TIM3_CR1 بافر کردن رجیستر پریود جهت جلوگیری از تغییر ناگهانی سقف شمارش
Mode PWM mode 1 بیت‌های OC1M[2:0] در TIM3_CCMR1 فعال‌سازی سطح High تا زمان کوچک‌تر بودن CNT از CCR1
Pulse (CCR1) 250 TIM3_CCR1 تعیین نسبت وظیفه اولیه سیگنال خروجی
Preload کانال؛ فعال‌سازی OC1PE در HAL Enable بیت OC1PE در TIM3_CCMR1 بافر کردن رجیستر مقایسه برای اعمال مقدار در مرز Update
CH Polarity High بیت CC1P در TIM3_CCER انتخاب High به عنوان سطح فعال خروجی

این تنظیمات در کد تولیدشده چه شکلی دارند؟

چند خط منتخب از MX_TIM3_Init در Core/Src/tim.c واقعی؛ این تابع را دوباره در main تعریف نکنید. تنظیم را از IOC تغییر دهید و کد را بازتولید کنید. OC1PE درون HAL_TIM_PWM_ConfigChannel همین نسخه فعال می‌شود.

  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 71;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 999;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 250;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;

در HAL دقیق همین پروژه، TIM_Base_SetConfig هنگام UG راه‌اندازی موقتاً URS را فعال می‌کند تا UIF ناشی از آن ایجاد نشود؛ سپس CR1 را برمی‌گرداند. این رفتار را با UG صریح کاربر پس از راه‌اندازی یکی نگیرید. ثبت نسخهٔ درایور برای چنین تفاوت‌هایی مهم است.

چگونه PWM را شروع، تغییر و متوقف کنیم؟

پس از تولید کدهای اولیه توسط CubeMX، کنترل خروجی در دست نرم‌افزار قرار می‌گیرد. درایور HAL توابع استانداردی را برای شروع، تغییر و توقف سیگنال فراهم کرده است.

آغاز سیگنال با HAL_TIM_PWM_Start

برای آنکه موج مربعی روی پایه فیزیکی ظاهر شود، باید خروجی کانال و شمارنده تایمر را با دستور زیر روشن کنیم:

در Core/Src/main.c، پس از MX_TIM3_Init و داخل USER CODE 2:

/* USER CODE BEGIN 2 */
  if (HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1) != HAL_OK)
  {
    lesson_hal_error = 1U;
    Error_Handler();
  }
  /* USER CODE END 2 */

این تابع در لایه رجیستری، بیت CC1E را در رجیستر TIM3_CCER یک می‌کند تا بافر خروجی به پایه فیزیکی متصل شود و سپس بیت CEN در TIM3_CR1 را برای به حرکت درآوردن شمارنده فعال می‌سازد.

تغییر Duty با نوشتن مقدار مقایسه

در حین اجرای برنامه، برای تغییر دادن نسبت وظیفه نیازی به توقف تایمر نیست. مناسب‌ترین روش، نوشتن مستقیم مقدار جدید درون رجیستر مقایسه با ماکروی کارآمد زیر است:

دستورهای واقعی case 0 در USER CODE 3 فایل main.c؛ خود switch و حلقه در پروژهٔ کامل هستند:

        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 250U);
        HAL_Delay(1000U);
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500U);
        HAL_Delay(1000U);
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 750U);
        HAL_Delay(1000U);

این ماکرو مقدار مقایسهٔ کانال انتخاب‌شده را می‌نویسد. چون OC1PE فعال است، مقدار در بخش پیش‌بارگذاری می‌ماند تا Update مجاز آن را اعمال کند. اگر چند بار پیش از یک Update بنویسید، آخرین مقدار موجود منتقل می‌شود؛ Preload صفِ همهٔ درخواست‌ها نیست. این قطعه از case 0 حلقهٔ اصلی پروژه آمده است؛ برای اجرا آن را همراه پروژهٔ کامل استفاده کنید.

توقف سیگنال و مدیریت الزامات سطح ایمن پایه

هنگامی که کار سیستم به پایان می‌رسد، با فراخوانی دستور توقف می‌توان خروجی تایمر را قطع کرد:

تابع واقعی در USER CODE 4 فایل main.c؛ در حالت 3 حلقه فراخوانی می‌شود:

static void PWM_StopLow(void)
{
  GPIO_InitTypeDef pin = {0};
  if (HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1) != HAL_OK)
  {
    lesson_hal_error = 2U;
    Error_Handler();
  }
  HAL_GPIO_WritePin(PWM_OUT_GPIO_Port, PWM_OUT_Pin, GPIO_PIN_RESET);
  pin.Pin = PWM_OUT_Pin;
  pin.Mode = GPIO_MODE_OUTPUT_PP;
  pin.Pull = GPIO_NOPULL;
  pin.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(PWM_OUT_GPIO_Port, &pin);
}

HAL_TIM_PWM_Stop کانال را غیرفعال می‌کند و در شرایط مجاز شمارنده را نیز متوقف می‌کند؛ آن را فرمان عمومیِ «تبدیل پایه به GPIO خروجی Low» ندانید. برای داشتن سطح Low مشخص در این نمونه، تابع PWM_StopLow ابتدا Stop را بررسی می‌کند، مقدار خروجی GPIO را Low می‌نویسد و PA6 را به Output Push-Pull تبدیل می‌کند؛ یعنی پایه به‌طور فعال صفر منطقی را می‌راند.

این روش مربوط به حالت عادیِ MCU تغذیه‌شده و اجرای صحیح برنامه است؛ مدار ایمنیِ مستقل برای Reset یا قطع تغذیه نیست. برای بار قدرت، طراحی درایور و وضعیت پیش‌فرض سخت‌افزاری باید جدا بررسی شود. در این درس فقط خروجی منطقی کم‌توان را آزمایش می‌کنیم.

در عمل چه نتایجی می‌بینیم و چطور مدار را آزمایش کنیم؟

برای آزمایش پیشنهادی، برد، ST-LINK با SWD، اسیلوسکوپ و پروب مناسب سیگنال 3.3V لازم است. نوک پروب را به PA6 نام‌گذاری‌شدهٔ برد و گیرهٔ زمین را به GND همان مدار وصل کنید. ورودی اسیلوسکوپ پرامپدانس باشد، نه بار 50Ω؛ اگر پروب روی 10x است، ضریب کانال دستگاه نیز 10x تنظیم شود. تغذیه را از ورودی مجاز برد بدهید؛ 5V را به PA6 یا پایهٔ 3V3 وارد نکنید. شکل، اتصال منطقی است و شمارهٔ هدر را از شمارهٔ پایهٔ تراشه نتیجه نگیرید.

اتصال منطقی پراب به PA6 و زمین مشترک برد
شماتیک تألیفی اتصال PA6 با AF2 و TIM3_CH1، پایهٔ فیزیکی 16 در LQFP48، به نوک پراب 10x با ورودی امپدانس‌بالا و اتصال گیره به GND برد. PB6 و LED کاربر مسیر جدا دارند. ورودی اسمی برد 5V و منطق داخلی 3V3 است؛ شکل پیشنهاد تغذیهٔ برد از پایهٔ 3V3 یا شماره‌گذاری هدرها نیست. برای دیدن جزئیات، تصویر را باز کنید.

آزمایش 1: ارزیابی شکل‌موج 1 کیلوهرتز در سه نسبت وظیفه

همهٔ نتیجه‌های زیر پیش‌بینی‌اند؛ پروگرام، اجرای برد و ثبت اسیلوسکوپ در تهیهٔ این مقاله انجام نشده‌اند. ابتدا جدول زمان و میانگین ولتاژ را خودتان حساب کنید. پروژه را بسازید و پروگرام کنید و در اجرای آزاد اندازه بگیرید؛ توقف دیباگر می‌تواند مشاهدهٔ زمان را تغییر دهد. با lesson_mode=0، برنامه 25%، 50% و 75% را با HAL_Delay(1000) به‌ترتیب انتخاب می‌کند. این تأخیر نرم‌افزاری حدود 1s است، نه تضمین تغییر دقیق در هر ثانیه؛ خود دورهٔ PWM را سخت‌افزار تعیین می‌کند.

این قطعه از switch در USER CODE 3 است؛ تابع کامل PWM_PreloadTrial و نگهبان انتظار در پروژه آمده‌اند:

      case 1U:
        PWM_PreloadTrial(1U);
        break;
      case 2U:
        PWM_PreloadTrial(0U);
        break;

برای شمارش صعودی، PWM1 و قطبیت High با PSC=71 و ARR=999، انتظار اسمی چنین است. میانگین و مقدار مؤثر RMS یک کمیت نیستند؛ جدول فقط میانگین را نشان می‌دهد.

CCR1 Duty T T_ON میانگین ایده‌آل؛ High=3.3V و Low=0V
250 25% 1000us 250us 0.825V
500 50% 1000us 500us 1.65V
750 75% 1000us 750us 2.475V

دوره و زمان High را برای هر گام در کاربرگ همراه ثبت کنید؛ تلرانس کلاک، ولتاژ، بار، تنظیم پروب و دقت دستگاه را نیز بنویسید. معیار پذیرش، سازگاری با این محاسبه در حدود خطای واقعی ابزار و کلاک است؛ عدد محاسبه‌شده را به‌جای نتیجهٔ اندازه‌گیری وارد نکنید.

آزمایش 2: بازتولید پالس ناقص با خاموش کردن رجیستر پیش‌بارگذاری

برای مقایسهٔ کنترل‌شده، در اجرای آزاد و با کمک متغیر lesson_mode، حالت 1 را برای Preload روشن و حالت 2 را برای خاموش انتخاب کنید. کد ابتدا CCR1=750 را در یک مرز دوره آماده می‌کند، سپس پس از رسیدن شمارنده به حوالی 500، CCR1=250 می‌نویسد. برای شکار دورهٔ گذار از Trigger و ثبت تک‌مرحله‌ای مناسب دستگاه استفاده کنید؛ هر چرخهٔ صفحه لزوماً پالس گذار نیست. مسیر خاموش‌کردن Preload، آزمایش عمدی اثر تغییر ناهم‌زمان است و الگوی پیشنهادی PWM پیوستهٔ برنامهٔ نهایی نیست. تأخیر Polling و وقفه‌ها مانع ادعای نوشتن دقیقاً در 500us می‌شوند.

انتظارهای فاز در کد با تابع PWM_WaitPhase محدود شده‌اند؛ اگر انتظار بیش از 20 تیک HAL طول بکشد، خطای 5 ثبت، خروجی Low و Error_Handler اجرا می‌شود. این نگهبان به پیشرفت SysTick وابسته است و Watchdog مستقل نیست.

برای آزمایش Stop، lesson_mode=3 را انتخاب کنید. برای آزمودن مقدار ثابت، حالت 4 و lesson_duty_permille در بازهٔ 0 تا 1000 را به کار ببرید؛ مثلاً 250 یعنی 25% و 1000 یعنی 100% در ARR ثابت 999. مقدار بزرگ‌تر خطای 4 می‌دهد. تغییر mode در حالت پله‌ای ممکن است تا پایان دور فعلی حلقه، حدود 3s، منتظر بماند؛ این کنترل دیباگر فرمان توقف اضطراری نیست. برگشت از حالت 3، تنظیم AF و PWM را از تابع بازگشت پروژه برقرار می‌کند. محوشدن تدریجی نور هنوز تمرین است و در حالت پیش‌فرض اجرا نمی‌شود.

اگر خروجی مطابق انتظار نبود چه کنیم؟

از مسیر واقعی سیگنال شروع کنید: کلاک TIM3، تنظیم شمارش و کانال، شروع موفق HAL و در پایان نگاشت PA6. این بررسی‌ها به پیدا کردن علت کمک می‌کنند:

  1. فراموش کردن تابع استارت: پیکربندی CubeMX عالی است، اما شکل‌موجی تولید نمی‌شود. دلیل آن فراموش کردن فراخوانی HAL_TIM_PWM_Start در بدنه اصلی برنامه است.
  2. اشتباه گرفتن پایه خروجی با LED برد: تلاش برای دیدن تغییر نور روی LED متصل به PB6، در حالی که تایمر روی PA6 تنظیم شده است.
  3. خطای محاسباتی مخرج فرمول: فراموش کردن عدد 1 در عبارت ARR + 1 یا PSC + 1 که محاسبهٔ فرکانس یا Duty را تغییر می‌دهد.
  4. نادیده گرفتن دوبرابرکننده کلاک APB1: استفاده از عدد 36 مگاهرتز به جای 72 مگاهرتز در محاسبات دستی که سبب می‌شود فرکانس عملی دقیقاً دو برابر مقدار پیش‌بینی‌شده باشد.
  5. اتصال مستقیم موتور به پایه میکروکنترلر: پایه‌های میکروکنترلر حداکثر توانایی تامین جریانی در حدود چند میلی‌آمپر را دارند. اتصال مستقیم بار القایی به پایه میکروکنترلر می‌تواند جریان برگشتی شدیدی ایجاد کرده و تراشه را بسوزاند. همواره از ترانزیستور، ماسفت یا درایور مناسب مجهز به دیود هرزگرد (Flyback Diode) استفاده کنید.

پرسش‌های خودآزمایی

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

پرسش 1: با وجود آنکه کلاک گذرگاه APB1 برابر 36 مگاهرتز است، چرا کلاک تایمر 3 برابر 72 مگاهرتز محاسبه شد؟
پاسخ: زیرا در مسیر کلاک APB1 به TIM3 این تراشه، وقتی مقسم APB1 غیر از 1 باشد، سخت‌افزار فرکانس ساعت ورودی تایمرهای آن گذرگاه را به طور خودکار در 2 ضرب می‌کند (36 × 2 = 72 MHz).

پرسش 2: در شمارش صعودی با ARR = 999، دقیقاً چند پله زمانی در یک دوره تناوب وجود دارد و چند سطح گسسته برای تنظیم نسبت وظیفه در دسترس است؟
پاسخ: تعداد پله‌ها یا کوانتاهای زمانی برابر با N = ARR + 1 = 1000 پله است. از آنجا که مقدار رجیستر مقایسه می‌تواند از 0 تا 1000 تغییر کند، در مجموع N + 1 = 1001 سطح گسسته (شامل صفر درصد تا صد درصد کامل) وجود دارد.

پرسش 3: اگر ARR = 999 باشد و کاربر به اشتباه مقدار CCR = 1500 را در رجیستر بنویسد، خروجی چه رفتاری نشان خواهد داد؟
پاسخ: شمارنده حداکثر تا 999 می‌شمارد؛ بنابراین شرط CNT < CCR همواره در طول تمام چرخه برقرار است. در نتیجه خروجی به طور دائمی در سطح High باقی می‌ماند و نسبت وظیفه روی 100% اشباع می‌شود (نه فراتر از 100%).

پرسش 4: نقش کلیدی بیت OC1PE چیست و عدم فعال‌سازی آن چه اثری دارد؟
پاسخ: این بیت بافر پیش‌بارگذاری رجیستر مقایسه کانال 1 را فعال می‌کند. اگر فعال نباشد، هرگونه تغییر در مقدار CCR بلافاصله در وسط دوره به مقایسه‌گر اعمال می‌شود که می‌تواند منجر به تولید پالس ناقص یا گلیچ گردد.

پرسش 5: چرا برای داشتن خروجی Low مشخص، Stop به‌تنهایی جای تنظیم GPIO را نمی‌گیرد؟
پاسخ: زیرا Stop کانال را غیرفعال می‌کند، اما جای تنظیم صریح GPIO خروجی Low را نمی‌گیرد. برای نیاز این نمونه، PWM_StopLow هم Stop و هم تغییر تنظیم پایه را انجام می‌دهد؛ وضعیت Reset یا قطع برق باید در طراحی مدار جدا بررسی شود.

پرسش 6: با ARR=65535 و CCR شانزده‌بیتی، چرا در PWM1 صعودی و قطبیت High، مقایسه به‌تنهایی 100% کامل نمی‌دهد؟
پاسخ: در شمارش صعودی، PWM1 و قطبیت High، برای 100% با روش مقایسه باید CCR=65536 باشد، ولی CCR شانزده‌بیتی حداکثر 65535 است. بنابراین با همین مد و قطبیت، بالاترین کسر High برابر 65535/65536 است. راه دیگر، انتخاب آگاهانهٔ حالت خروجی ثابت مناسب است؛ نوشتن عدد خارج ظرفیت راه‌حل نیست.

تمرین‌های کاربردی و توسعه مهارت

  1. تمرین 1 (تنظیم بالاترین تفکیک‌پذیری در 20 کیلوهرتز): مقادیر PSC و ARR را به گونه‌ای محاسبه کنید که در کلاک 72 مگاهرتز، سیگنال PWM با فرکانس دقیق 20 کیلوهرتز و بالاترین تفکیک‌پذیری ممکن تولید شود. (راهنمایی: مقدار PSC = 0 و ARR = 3599 منجر به 3600 پله زمانی با تفکیک‌پذیری 0.0278% می‌شود).
  2. تمرین 2، تنفس LED خارجی: ابتدا مدار LED و مقاومت سری مناسب را مشخص کنید. برای نمونهٔ آموزشی با ARR=999 و x در بازهٔ 0 تا 1، از رابطهٔ CCR = round(1000 × x^γ) استفاده کنید؛ x را آهسته از 0 تا 1 و برعکس تغییر دهید. γ=2.2 فقط یک انتخاب تجربی برای مقایسه با تغییر خطی است، نه قانون همگانی چشم یا کالیبراسیون LED. خروجی را به 0 تا 1000 محدود کنید و اگر ARR را تغییر دادید، N=ARR+1 و ظرفیت CCR را دوباره بررسی کنید. این تمرین در پروژهٔ پیش‌فرض پیاده نشده است.
  3. تمرین 3 (دو کانال هم‌فرکانس با نسبت وظیفه مستقل): کانال 2 تایمر 3 (TIM3_CH2) را روی پایه PA7 (پایه فیزیکی شماره 17 با نگاشت AF2) فعال کنید. با استفاده از شمارنده مشترک TIM3، برنامه‌ای بنویسید که هر دو کانال با فرکانس یکسان 1 کیلوهرتز کار کنند، اما کانال 1 دارای نسبت وظیفه 30% و کانال 2 دارای نسبت وظیفه 70% باشد.

وضعیت ساخت، اسناد مرجع و فایل‌های پروژه

پروژهٔ قابل دریافت بر پایهٔ کد واقعی CubeMX ساخته شده است. خلاصهٔ بررسی انجام‌شده در ادامه است؛ گزارش کامل نسخه‌ها، فایل‌ها و محدودیت‌ها داخل ZIP قرار دارد.

پروژهٔ واقعی gebrabit_lesson19_pwm_refresh با CubeMX 6.14.0، DB 6.0.140 و CubeF3 1.11.6 تولید و با GCC 13.2.1، ساخت پاک Debug و گزینه‌های -O0 -g3 -Wall -Werror بدون خطا و هشدار کامپایل شده است. Flash برابر 12668B و RAM ناحیهٔ لینک برابر 1672B است؛ مصرف زنده اندازه‌گیری نشده است. مدل محاسباتی و بررسی فایل‌ها 36 کنترل را گذرانده‌اند. این نتایج کامپایل و مدل‌اند؛ اجرای برد، محیط گرافیکی CubeIDE و اندازه‌گیری اسیلوسکوپ انجام نشده‌اند. خروجی پروژه CMake است؛ راه واردکردن یا تولید خروجی CubeIDE در راهنما آمده و اجرای آن ادعا نشده است.

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

فایل‌های کامل پروژه شامل پیکربندی CubeMX، کدهای مبدا و مستندات تکمیلی از پیوند زیر قابل دریافت هستند:

دانلود پروژه و فایل‌های همراه درس 19

پروژهٔ کامل با فایل IOC و سورس، ELF/MAP واقعی و گزارش کامپایل، مدل محاسباتی، کاربرگ آزمایش و 7 تصویر با فایل قابل ویرایش داخل ZIP است. ابتدا فایل را استخراج و README را بخوانید.

پیوند دانلود به نسخهٔ مشخص همراه مقاله متصل است. گزارش‌ها به معنی انجام آزمایش روی برد نیستند.

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

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

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