جزء نسخهٔ بررسی‌شده
برد و تراشه GB100ST، شماتیک V2.0، STM32F303CBT6، LQFP48
CubeMX و پایگاه داده 6.14.0 / DB.6.0.140
بستهٔ STM32CubeF3 V1.11.6؛ commit: 663d2baae3604244f90ed80d0d757df17e36ada9
HAL خانوادهٔ F3 1.5.8؛ commit بسته: 953955afe65f89e60e556bbcdba752597f5da65d
ساخت اجراشده Arm GNU GCC 13.2.1، CMake/Ninja، Debug، -Wall -Werror
CubeIDE در محیط ساخت حاضر اجرا نشده؛ تولید و ساخت پروژهٔ مخصوص CubeIDE هنوز نیازمند تکمیل است.

وضعیت: پیش‌نویس بازنویسی با کد کامپایل‌شده. ساخت کامل پروژهٔ CMake/GCC با -Wall و بدون خطا یا هشدار انجام شده است. خروجی تولیدشده از CubeMX واقعی است. آزمون روی GB100ST و اندازه‌گیری با اسیلوسکوپ انجام نشده است. ساخت در CubeIDE هنوز تأیید نشده است. فایل پروژه، اسکریپت محاسبات و گزارش ساخت از بخش «دانلود پروژه و فایل‌های همراه درس 19» در انتهای مقاله در دسترس‌اند.

1. پیش‌نیازها، هدف و مدل مفهومی سیگنال PWM

برای درک عمیق این مبحث، مطالعه دروس پیشین از جمله مفاهیم درخت توزیع کلاک در STM32 و اصول پیکربندی پایه ورودی/خروجی و Alternate Function الزامی است. همچنین مطالب ارائه شده در «درس 18: مبنای زمان و وقفه‌های تایمر عمومی» پایه مستقیم این آموزش به شمار می‌رود.

مدولاسیون پهنای پالس (Pulse Width Modulation یا PWM)، روشی استاندارد در سامانه‌های دیجیتال برای کنترل توان تحویلی به بارهای آنالوگ (مانند موتورهای DC، درایورهای سوئیچینگ و دیودهای نورافشان) از طریق یک سیگنال گسسته با فرکانس ثابت است. شکل‌موج دوره ای این سیگنال بر مبنای دور تناوب (TPWM) و زمان برقراری سطح فعال (TON) تعریف می‌شود. فرکانس سیگنال برابراست با معکوس دور تناوب:

fPWM = 1 / TPWM

نسبت وظیفه (Duty Cycle) به صورت کسر یا درصدی از زمان تناوب کل که سیگنال در سطح منطقی فعال سپری می‌کند بیان می‌شود:

Duty Cycle = TON / TPWM = TON × fPWM

ولتاژ میانگین ریاضی سیگنال در طول یک دوره تناوب کامل، متناسب با نسبت وظیفه و ولتاژ سطح منطقی بالا (VOH) تعیین می‌گردد. در میکروکنترلر STM32F303CBT6 که بر روی برد GebraBit GB100ST با رگولاتور AMS1117-3V3 در سطح ولتاژ 3٫3 ولت تغذیه می‌شود، ولتاژ میانگین خروجی با فرض ناچیز بودن افت ولتاژ درایور خروجی برابر است با:

Vavg = (1 / TPWM) ∫0TPWM v(t) dt = Duty Cycle × VDD = Duty Cycle × 3.3 V

میانگین ولتاژ با فرض سطح Low صفر و سطح High برابر 3٫3 ولت محاسبه شده است. پاسخ LED، فیلتر RC و موتور یکسان نیست و به بار و مدار راه‌انداز وابسته است. GPIO در این درس فقط به ابزار اندازه‌گیری یا LED کم‌جریان با مقاومت سری وصل می‌شود؛ موتور را مستقیم به پایه وصل نکنید.

2. PWM سخت‌افزاری و Toggle نرم‌افزاری

در آزمایش درس 18، پردازنده پس از رسیدن وقفه وضعیت GPIO را تغییر می‌دهد. تأخیر ورود به ISR و وقفه‌های با اولویت بالاتر می‌توانند زمان لبهٔ نرم‌افزاری را تغییر دهند. در PWM سخت‌افزاری، مقایسهٔ CNT و CCR در خود تایمر انجام می‌شود و ساخت هر لبه به اجرای ISR نیاز ندارد؛ وقفه و DMA در مثال حاضر فعال نشده‌اند.

دقت فرکانس همچنان به کلاک وابسته است. دورهٔ کلاک ورودی 72 مگاهرتزی حدود 13٫889 نانوثانیه است، اما با PSC=71 هر گام CNT در این مثال 1 میکروثانیه طول می‌کشد؛ گام تغییر پهنای پالس همین 1 میکروثانیه است. تفکیک زمانی را با دقت کریستال یا نبود مطلق Jitter اشتباه نگیرید.

3. ساختار درونی تایمر: زنجیره شمارنده (CNT)، تقسیم‌کننده (PSC) و مقایسه‌گر (CCR)

بلوک تولید PWM در تایمرهای عمومی STM32 (مانند TIM3) بر تعامل مداوم چهار رجیستر اساسی استوار است:

  • تقسیم‌کننده کلاک (TIMx_PSC): مقدار کلاک ورودی تایمر را به نسبت عدد 16 بیتی PSC + 1 کاهش می‌دهد تا سرعت گام‌های شمارنده تعیین شود.
  • شمارنده (TIMx_CNT): ثباتی 16 بیتی که با هر تیک خروجی مقسم، در حالت صعودی (Upcounting) یک واحد افزایش می‌یابد.
  • رجیستر بارگذاری خودکار (TIMx_ARR): حد بالای شمارش را تعیین می‌کند. شمارنده پس از رسیدن به این مقدار، سرریز شده و به صفر بازمی‌گردد. بنابراین تعداد کل بازه‌های زمانی در هر دوره تناوب برابر با ARR + 1 است.
  • رجیستر ثبت/مقایسه (TIMx_CCRx): مقداری که محتوای آن به صورت بلادرنگ در هر چرخه کلاک با مقدار فعلی CNT مقایسه می‌شود. نتیجه این مقایسه دیجیتال، وضعیت پایه خروجی متناظر را تعیین می‌کند.
شکل 1 ـ مدل شمارنده و مقایسه‌گر؛ خروجی روی PA6.
شکل 1 ـ مدل شمارنده و مقایسه‌گر؛ خروجی روی PA6.

4. روابط ریاضی دقیق، مثال محاسباتی 1 کیلوهرتز و مصالحه تفکیک‌پذیری

در معماری توزیع ساعت STM32F303، باس APB1 دارای حداکثر فرکانس 36 مگاهرتز است. بنابراین تقسیم‌کننده کلاک APB1 بر روی 2 تنظیم می‌شود (PCLK1 = 36 MHz). بر اساس مستند فنی RM0316 (بخش RCC)، هرگاه ضریب تقسیم باس APB مخالف 1 باشد، سخت‌افزار فرکانس کلاک ورودی تایمرهای متصل به آن باس را دو برابر می‌کند. در نتیجه، فرکانس کلاک واحد تایمر 3 برابراست با:

fTIM3CLK = 2 × PCLK1 = 2 × 36 MHz = 72 MHz

روابط زیر تنها و فقط در شرایط زیر صادق هستند:

  1. شمارش صعودی (Upcounting Mode: DIR = 0 در رجیستر TIMx_CR1).
  2. حالت تراز لبه‌ای (Edge-aligned: CMS = 00 در رجیستر TIMx_CR1).
  3. مد کاری PWM Mode 1 (OCxM = 110 در رجیستر TIMx_CCMRx).
  4. قطبیت فعال بالا (Active-High: CCxP = 0 در رجیستر TIMx_CCER).

فرمول محاسبه فرکانس خروجی:

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

فرمول محاسبه نسبت وظیفه و پهنای پالس سطح بالا:

Duty Cycle = CCR1 / (ARR + 1)
THIGH = CCR1 × (PSC + 1) / fTIM3CLK

مثال طراحی فرکانس 1 کیلوهرتز

فرض کنید هدف تولید موج مربعی با فرکانس دقیق 1 kHz باشد. ضریب کل تقسیم کلاک مورد نیاز برابر است با:

Total Division = fTIM3CLK / fPWM = 72,000,000 / 1,000 = 72,000

حال اگر بخواهیم پری‌اسکالر را صفر قرار دهیم (PSC = 0 یعنی PSC + 1 = 1)، مقدار مورد نیاز برای رجیستر دوره تناوب خواهد بود:

ARR + 1 = 72,000 ⇒ ARR = 71,999

محدودیت سخت‌افزاری: تایمر 3 در میکروکنترلر STM32F303CBT6 یک تایمر 16 بیتی است. حداکثر عددی که در رجیستر 16 بیتی ARR جا می‌گیرد برابر با 216 - 1 = 65,535 است. عدد 71,999 بزرگ‌تر از سقف رجیستر بوده و دچار سرریز سخت‌افزاری می‌شود؛ بنابراین اجرای فرکانس 1 کیلوهرتز با PSC = 0 در تایمر 3 غیرممکن است.

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

برای دستیابی به فرکانس دقیق 1 کیلوهرتز، دو رویکرد مهندسی رایج وجود دارد:

  1. رویکرد گام زمانی رند (تنظیمات آموزشی مرجع): با انتخاب PSC = 71، فرکانس کلاک شمارنده برابر با 72 MHz / 72 = 1 MHz می‌شود؛ در نتیجه طول هر تیک شمارنده دقیقاً 1 میکروثانیه است. برای رسیدن به پریود 1 میلی‌ثانیه‌ای (1 کیلوهرتز)، مقدار رجیستر بارگذاری خودکار برابر با ARR = 999 (ARR + 1 = 1000 بازه) تنظیم می‌شود.
    در این پیکربندی، تعداد سطوح گسسته برای تنظیم دیوتی سایکل برابر با 1000 بازه زمانی است که معادل تفکیک‌پذیری زیر خواهد بود:

    Resolution = log2(1000) ≈ 9.966 bits

    در این حالت مقادیر رجیستر مقایسه‌گر برای نسبت‌های وظیفه استاندارد به صورت زیر تعیین می‌شوند:

    • نسبت 25٪: CCR1 = 250 ⇒ THIGH = 250 μs (ولتاژ میانگین 0٫825 ولت)
    • نسبت 50٪: CCR1 = 500 ⇒ THIGH = 500 μs (ولتاژ میانگین 1٫650 ولت)
    • نسبت 75٪: CCR1 = 750 ⇒ THIGH = 750 μs (ولتاژ میانگین 2٫475 ولت)
  2. رویکرد بیشترین تفکیک‌پذیری در 1 کیلوهرتز: کوچک‌ترین عدد صحیحی که ضریب تقسیم را در محدوده 16 بیت نگه می‌دارد، PSC = 1 (PSC + 1 = 2) است. در این حالت:

    ARR + 1 = 72,000 / 2 = 36,000 ⇒ ARR = 35,999

    تعداد بازه‌های شمارش 36,000 گام است که تفکیک‌پذیری معادل آن برابر است با:

    Resolution = log2(36,000) ≈ 15.136 bits

    توجه کنید که این تفکیک‌پذیری معادل 15٫136 بیت است و ادعای دستیابی به تفکیک‌پذیری کامل 16 بیت (65,536 سطح) در فرکانس 1 کیلوهرتز روی کلاک 72 مگاهرتز نادرست است، زیرا نیازمند کلاک 65٫536 مگاهرتز خواهد بود.

5. حالت‌های عملکرد خروجی: تفاوت PWM Mode 1 و Mode 2، قطبیت و تراز زمانی

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

مد مقایسه‌گر خروجی (OCxM در رجیستر CCMRx)

در حالت شمارش صعودی:

  • PWM Mode 1: تا زمانی که مقدار شمارنده اکیداً کوچک‌تر از رجیستر مقایسه باشد (CNT < CCRx)، کانال در وضعیت فعال (Active) و در غیر این صورت در وضعیت غیرفعال (Inactive) قرار می‌گیرد.
  • PWM Mode 2: تا زمانی که مقدار شمارنده اکیداً کوچک‌تر از رجیستر مقایسه باشد (CNT < CCRx)، کانال در وضعیت غیرفعال و در غیر این صورت در وضعیت فعال است. یعنی با فرض یکسان بودن قطبیت، نسبت وظیفه PWM Mode 2 متمم نسبت وظیفه PWM Mode 1 خواهد بود:
    DutyPWM2 = 1 – DutyPWM1

قطبیت کانال خروجی (CCxP در رجیستر CCER)

بیت CCxP مستقیماً سطح ولتاژ منطقی متناظر با وضعیت «فعال» را معین می‌کند:

  • Active-High (CCxP = 0): وضعیت فعال برابر با سطح ولتاژ منطقی بالا (3٫3 ولت) و وضعیت غیرفعال برابر با زمین (0 ولت) است.
  • Active-Low (CCxP = 1): برعکس حالت قبل، وضعیت فعال معادل ولتاژ صفر و وضعیت غیرفعال معادل 3٫3 ولت خواهد بود. این تغییر قطبیت، خروجی الکتریکی فیزیکی پین را اینورت می‌کند.

تراز لبه‌ای و مرکزی

در حالت لبه‌ای این مثال، شمارنده فقط رو به بالا می‌رود. حالت مرکزی شمارش رفت‌وبرگشتی دارد و محاسبهٔ دورهٔ آن متفاوت است؛ فرمول صعودی این درس را به آن تعمیم ندهید. تنظیمات CMS همچنین بر انتخاب رخدادهای وقفهٔ مقایسه اثر دارند. در این پروژه CMS=00 است.

شکل 3 ـ شکل‌موج‌های محاسبه‌شده؛ این تصویر ثبت اسیلوسکوپ نیست.
شکل 3 ـ شکل‌موج‌های محاسبه‌شده؛ این تصویر ثبت اسیلوسکوپ نیست.

6. رفتار در شرایط مرزی و تحلیل سقف رجیستر 16 بیتی

رفتار خروجی در مقادیر مرزی رجیسترها طبق مستند مرجع RM0316 به شرح زیر تثبیت شده است (با فرض PWM Mode 1، شمارش صعودی و قطبیت Active-High):

  • مقدار مرزی CCR1 = 0: شرط CNT < CCR1 در هیچ چرخه‌ای برقرار نخواهد شد (چرا که کمترین مقدار شمارنده صفر است). بنابراین خروجی به صورت پیوسته در سطح غیرفعال (0 ولت یعنی Duty Cycle = 0%) پایدار می‌ماند.
  • مقدار مرزی CCR1 > ARR: شرط CNT < CCR1 در تمامی لحظات چرخه شمارش صادق است، زیرا شمارنده هیچ‌گاه به فراتر از مقدار ARR دسترسی پیدا نمی‌کند. خروجی به صورت پیوسته در سطح فعال (3٫3 ولت یعنی Duty Cycle = 100%) قرار می‌گیرد.
  • رفتار ویژه در سقف 16 بیتی (ARR = 65,535): اگر ARR = 0xFFFF تنظیم شود، تعداد کل بازه‌های زمانی برابر با ARR + 1 = 65,536 است. برای رسیدن به نسبت وظیفه 100٪ بر اساس فرمول خطی، باید مقدار رجیستر مقایسه برابر با 65,536 قرار گیرد. اما ثبات CCR1 یک رجیستر 16 بیتی است و بیشترین مقداری که در آن ذخیره می‌شود 65,535 است.
    هنگامی که CCR1 = 65,535 باشد، در چرخه آخر که CNT = 65,535 می‌شود، شرط CNT < CCR1 نقض شده و پایه خروجی دقیقاً به مدت یک تیک شمارنده پس از تقسیم PSC به سطح غیرفعال می‌رود! بنابراین نسبت وظیفه به دست آمده برابر با:

    Duty = 65,535 / 65,536 ≈ 99.99847 %

    خواهد بود و به طور خالص 100٪ نمی‌شود، مگر آنکه از مدهای اجباری نرم‌افزاری (Force Inactive/Active) یا حالت Forced Active با قطبیت درست بررسی شود؛ Forced Inactive برای تولید High در قطبیت فعال‌بالای این مثال مناسب نیست.

7. سازوکار بافر سایه (Shadow)، رجیسترهای Preload و خطرات پالس ناقص

رجیسترهای کنترل‌کننده زمان‌بندی تایمر شامل دو لایه فیزیکی هستند: «رجیستر پیش‌بارگذاری (Preload)» که توسط پردازنده خوانده و نوشته می‌شود، و «رجیستر سایه (Shadow)» که در مدارات ترتیبی سخت‌افزار به صورت مستقیم با مقایسه‌گر و شمارنده کار می‌کند.

  • رجیستر تقسیم‌کننده کلاک (TIMx_PSC): همواره و بدون استثنا دارای بافر سایه سخت‌افزاری است. تغییر این رجیستر تا زمان رخ دادن رخداد به‌روزرسانی (Update Event یا UEV) اعمال نمی‌شود.
  • رجیستر بارگذاری مجدد (TIMx_ARR): به کارگیری بافر سایه برای آن اختیاری بوده و توسط بیت ARPE در رجیستر TIMx_CR1 کنترل می‌شود.
  • رجیسترهای مقایسه‌گر (TIMx_CCRx): بافر سایه این بخش توسط بیت OCxPE در رجیستر TIMx_CCMRx فعال می‌گردد.
شکل 2 ـ انتقال مقدار جدید از Preload به رجیستر فعال در UEV.
شکل 2 ـ انتقال مقدار جدید از Preload به رجیستر فعال در UEV.

انتقال اطلاعات از رجیسترهای Preload به ثبات‌های Shadow، در زمان سرریز تایمر و وقوع خودکار Update Event (UEV) رخ می‌دهد. این سازوکار انتقال همگام باعث می‌شود حتی اگر در اواسط یک تناوب مقدار دیوتی سایکل تغییر یابد، مقدار جدید تا آغاز دوره بعدی در حالت تعلیق بماند و شکل‌موج دوره فعلی مخدوش نشود.

چرا نباید از بیت UG برای تغییر نسبت وظیفه در حین کار استفاده کرد؟

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

نتیجه این ریست ناگهانی در حین اجرای دوره شمارش، شکسته‌شدن دوره تناوب فعلی، ایجاد پالس‌های بسیار باریک ناخواسته (Runt Pulses) و به هم ریختن زمان‌بندی ادوات حساس (نظیر ماسفت‌های قدرت) خواهد بود. بنابراین تغییر نسبت وظیفه در زمان اجرای سیستم فقط و فقط باید از طریق نوشتن در ثبات مقایسه (با فعال بودن OCxPE) انجام پذیرد تا با چرخه طبیعی UEV در پایان پریود همگام شود.

ماهیت ناهماهنگ غیرفعال‌سازی OCxPE

اگر بافر مقایسه‌گر غیرفعال شود (OCxPE = 0)، هر بار نوشتن پردازنده در ثبات CCR1 بلافاصله روی مقایسه همان سیکل اثر می‌گذارد. با این وجود، صرف خاموش بودن OCxPE به معنای تضمین رؤیت پالس ناقص در هر سیکل نیست؛ زیرا اگر پردازنده درست زمانی ثبات را بازنویسی کند که مقدار شمارنده در آن لحظه کمتر از هر دو مقدار قبلی و جدید CCR1 باشد، گذار سیگنال بدون عیب ثبت می‌شود. پالس ناقص زمانی ایجاد می‌گردد که شمارنده از مقدار جدید عبور کرده باشد اما پردازنده ناگهان آستانه را به مقداری کمتر از موقعیت فعلی CNT تغییر دهد.

8. پیکربندی در STM32CubeMX و نگاشت تنظیمات به رجیسترهای سخت‌افزاری

بر اساس دیتاشیت رسمی تراشه (DS9118 جدول Alternate Functions برای پورت A) و نقشه شماتیک GebraBit GB100ST V2.0:

  • پایه PA6 در وضعیت عملکرد جایگزین AF2 به کانال 1 تایمر 3 متصل می‌شود (TIM3_CH1).
  • این پین بر روی هدرهای برد در دسترس است و مستقیماً سیگنال سخت‌افزاری مقایسه‌گر تایمر را ارائه می‌دهد.

نکته بسیار مهم در ارتباط با سخت‌افزار برد: دیود نورافشان کاربر (User LED) بر روی برد GebraBit GB100ST به پایه PB6 و از طریق یک ترانزیستور سوئیچینگ NPN (با منطق فعال بالا) متصل شده است. بنابراین سیگنال خروجی کانال 1 تایمر 3 روی پایه PA6 هیچ اتصال فیزیکی مستقیمی با LED کاربر روی برد ندارد. برای مشاهدهٔ روشنایی، از LED خارجی با مقاومت سری 330 اهم روی PA6 استفاده کنید. در این پروژه PA6 را به PB6 جامپر نکنید؛ نمونهٔ اصلی فقط به اسیلوسکوپ نیاز دارد.

پارامتر در رابط گرافیکی STM32CubeMX مقدار تنظیمی برای پروژه رجیستر و بیت‌های متناظر در سخت‌افزار تأثیر عملکردی در تایمر 3
Prescaler (PSC) 71 TIM3->PSC = 71 تقسیم فرکانس 72 مگاهرتز بر 72 و دستیابی به کلاک شمارش 1 مگاهرتز
Counter Mode Up TIM3->CR1: DIR = 0 شمارش صعودی از صفر تا مقدار سقف ARR
Counter Period (AutoReload Register) 999 TIM3->ARR = 999 تعیین 1000 گام زمانی معادل دور تناوب 1 میلی‌ثانیه (فرکانس 1 کیلوهرتز)
auto-reload preload Enable TIM3->CR1: ARPE = 1 بافری‌کردن رجیستر دوره تناوب و جلوگیری از تغییرات اخلال‌گرانه در حین شمارش
Channel 1 PWM Generation CH1 CC1E = 1 پس از HAL_TIM_PWM_Start انتخاب PWM در CubeMX حالت کانال را تنظیم می‌کند؛ Init به‌تنهایی خروجی را Start نمی‌کند
Mode PWM mode 1 TIM3->CCMR1: OC1M[2:0] = 110b تعریف وضعیت فعال به شرط CNT < CCR1
Pulse 250 TIM3->CCR1 = 250 تنظیم مقدار اولیه نسبت وظیفه بر روی 25٪
Output compare preload Enable TIM3->CCMR1: OC1PE = 1 انتقال همگام تغییرات Duty Cycle به رجیستر سایه فقط در زمان رخداد UEV
CH Polarity High TIM3->CCER: CC1P = 0 تطبیق سطح فعال با ولتاژ 3٫3 ولت (High)
GPIO Mode PA6 Alternate Function Push-Pull GPIOA->MODER: MODER6 = 10b
GPIOA->AFR[0]: AFRL6 = 0010b
واگذاری هدایت فیزیکی پین به واحد AF2 (TIM3_CH1)

9. کنترل با HAL و توقف خروجی

در نسخهٔ درایور همین پروژه، HAL_TIM_PWM_Start ابتدا آماده بودن وضعیت کانال را بررسی می‌کند؛ اگر آماده نباشد HAL_ERROR برمی‌گرداند. سپس CC1E و شمارنده را فعال می‌کند. در main.c مقدار برگشتی بررسی شده و خطای شروع با lesson_hal_error=1 و ورود به Error_Handler قابل تشخیص است.

ماکروی __HAL_TIM_SET_COMPARE مقدار CCR1 را می‌نویسد. برای حالت انتخاب‌شده و ARR=999، مقادیر 0 تا 1000 بازهٔ صفر تا صد درصد را پوشش می‌دهند. با OC1PE=1 مقدار جدید در مرز UEV به بخش فعال منتقل می‌شود.

توقف و Low مشخص

HAL_TIM_PWM_Stop کانال را غیرفعال و وضعیت نرم‌افزاری آن را READY می‌کند. غیرفعال‌سازی شمارنده در ماکروی HAL به فعال نبودن دیگر کانال‌ها وابسته است؛ پروژهٔ حاضر فقط کانال 1 دارد. این تابع تنظیم GPIO را به Output تغییر نمی‌دهد، پس از نام Stop به‌تنهایی سطح Low رانده‌شده نتیجه نگیرید.

در حالت 3 برنامه، PWM_StopLow پس از توقف، ODR پایه را Reset می‌کند و PA6 را به GPIO Output Push-Pull تبدیل می‌کند. هنگام خروج از حالت 3، PWM_RestoreOutput پایه را به AF2 برمی‌گرداند و قبل از Start، در حالی که شمارنده متوقف است، مقادیر بافر را با UG بارگذاری می‌کند. این UG پیش از شروع با تحمیل UG در میانهٔ یک دوره متفاوت است. فرایند توقف/شروع بدون پالس گذرا برای درایو قدرت تضمین نشده و موضوع این آزمایش نیست.

10. کدهای اعتبارسنجی‌شده پروژه

بخش‌های زیر از پروژهٔ واقعی تولیدشده با CubeMX و ساخته‌شده با CMake/GCC استخراج شده‌اند. این گزارش تأیید ساخت در CubeIDE یا آزمون روی برد نیست:

پیکربندی اولیهٔ تولیدشده توسط CubeMX؛ فایل tim.c

فایل واقعی Core/Src/tim.c، بخشی از تابع تولیدشدهٔ MX_TIM3_Init؛ فقط برای مقایسه با خروجی CubeMX نمایش داده شده و محل درج کد کاربر نیست:

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;
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 250;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

کد منطق کاربر برای راه‌اندازی، تغییر پله‌ای نسبت وظیفه و خاموشی امن (main.c)

متغیرها؛ در main.c، بلوک USER CODE BEGIN PV

volatile uint32_t lesson_mode = 0U;
volatile uint32_t lesson_hal_error = 0U;
static uint32_t previous_mode = 0U;

اعلان تابع‌ها؛ بلوک USER CODE BEGIN PFP

static void PWM_StopLow(void);
static void PWM_RestoreOutput(void);
static void PWM_PreloadTrial(uint32_t enabled);

راه‌اندازی پس از MX_TIM3_Init؛ بلوک USER CODE BEGIN 2

if (HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1) != HAL_OK)
  {
    lesson_hal_error = 1U;
    Error_Handler();
  }

بدنهٔ حلقهٔ موجود؛ بلوک USER CODE BEGIN 3

uint32_t mode = lesson_mode;
    if (mode != previous_mode)
    {
      if (previous_mode == 3U)
      {
        PWM_RestoreOutput();
      }
      SET_BIT(TIM3->CCMR1, TIM_CCMR1_OC1PE);
      if (mode == 3U)
      {
        PWM_StopLow();
      }
      previous_mode = mode;
    }

    switch (mode)
    {
      case 0U:
        __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);
        break;
      case 1U:
        PWM_PreloadTrial(1U);
        break;
      case 2U:
        PWM_PreloadTrial(0U);
        break;
      case 3U:
        HAL_Delay(10U);
        break;
      default:
        lesson_hal_error = 3U;
        Error_Handler();
        break;
    }

تعریف تابع‌های کاربر؛ بلوک USER CODE BEGIN 4

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);
}

static void PWM_RestoreOutput(void)
{
  GPIO_InitTypeDef pin = {0};
  pin.Pin = PWM_OUT_Pin;
  pin.Mode = GPIO_MODE_AF_PP;
  pin.Pull = GPIO_NOPULL;
  pin.Speed = GPIO_SPEED_FREQ_LOW;
  pin.Alternate = GPIO_AF2_TIM3;
  HAL_GPIO_Init(PWM_OUT_GPIO_Port, &pin);
  SET_BIT(TIM3->CCMR1, TIM_CCMR1_OC1PE);
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 250U);
  __HAL_TIM_SET_COUNTER(&htim3, 0U);
  /* Timer is stopped here: latch the buffered values before restarting. */
  TIM3->EGR = TIM_EGR_UG;
  __HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
  if (HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1) != HAL_OK)
  {
    lesson_hal_error = 1U;
    Error_Handler();
  }
}

static void PWM_PreloadTrial(uint32_t enabled)
{
  /* Establish the same baseline at a complete period boundary. */
  SET_BIT(TIM3->CCMR1, TIM_CCMR1_OC1PE);
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 750U);
  while (__HAL_TIM_GET_COUNTER(&htim3) < 900U) {}
  while (__HAL_TIM_GET_COUNTER(&htim3) > 100U) {}

  if (enabled == 0U)
  {
    CLEAR_BIT(TIM3->CCMR1, TIM_CCMR1_OC1PE);
  }
  while (__HAL_TIM_GET_COUNTER(&htim3) < 500U) {}
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 250U);
  HAL_Delay(10U);
}

در فایل main.c تولیدشده، tim.h و gpio.h از قبل وارد شده‌اند. تابع main یا MX_TIM3_Init را دوباره تعریف نکنید. تمام بلوک‌های بالا عیناً از پروژهٔ ساخته‌شده استخراج شده‌اند. متغیر lesson_mode را در Live Expressions دیباگر تغییر دهید: 0 نمایش عادی، 1 آزمایش با Preload، 2 آزمایش بدون Preload، 3 توقف و خروجی Low. تغییر حالت از نمایش عادی ممکن است تا سه ثانیه صبر بخواهد. حلقه‌های انتظار در آزمایش Preload عمداً مسدودکننده‌اند و برای همین آزمایش محدود نوشته شده‌اند؛ الگوی برنامهٔ کاربردی هم‌زمان نیستند.

11. آزمایش: اول پیش‌بینی، سپس اندازه‌گیری

تجهیزات و اتصال‌ها

برد GB100ST، پروگرامر ST-LINK، منبع مجاز 5 ولت، اسیلوسکوپ و سیم اتصال لازم است. طبق شماتیک، تغذیه را به 5V و GND بدهید؛ پروب را به PA6 و زمین پروب را به GND وصل کنید. رابط SWD از PA13 و PA14 استفاده می‌کند و خروجی PWM روی PA6 است. این برد در این مثال کانکتور USB فرضی ندارد.

آزمایش اول: سه نسبت وظیفه

lesson_mode را صفر نگه دارید. قبل از پروگرام، دورهٔ 1 میلی‌ثانیه و مقادیر جدول را با verify_pwm.py محاسبه کنید. سپس پهنای High و Low و فرکانس را روی ابزار بخوانید. ولتاژهای جدول میانگین ایدئال‌اند؛ برای مقایسهٔ دقیق، VOH و VOL واقعی و مشخصات ابزار را هم در نظر بگیرید. مولتی‌متر DC به‌تنهایی شکل‌موج و فرکانس را تأیید نمی‌کند.

CCR1 نسبت وظیفه High Low میانگین ایدئال
250 25% 250 µs 750 µs 0.825 V
500 50% 500 µs 500 µs 1.650 V
750 75% 750 µs 250 µs 2.475 V

سه مقدار هر کدام حدود یک ثانیه نمایش داده می‌شوند. عدد 1 کیلوهرتز، مقدار نامی محاسبه‌شده است؛ نتیجهٔ اندازه‌گیری و خطای آن را خودتان ثبت کنید. هیچ سطر این جدول اندازه‌گیری‌شده نیست.

آزمایش دوم: مقایسهٔ کنترل‌شدهٔ Preload روشن و خاموش

از همان پروژهٔ کامپایل‌شده استفاده کنید. برای مقایسه، lesson_mode را ابتدا 1 و سپس 2 قرار دهید و برنامه را آزاد اجرا کنید. در هر آزمون، PWM_PreloadTrial مقدار 750 را با Preload فعال آماده می‌کند، گذر از انتهای دوره را انتظار می‌کشد و حدود CNT=500 مقدار CCR را به 250 تغییر می‌دهد. در حالت 1، OC1PE روشن می‌ماند؛ در حالت 2، درست پیش از تغییر خاموش می‌شود.

با ماندگاری تصویر یا Trigger پهنای پالس، گذارهای بین دوره‌ها را مقایسه کنید. انتظار مدل: حالت 1 مقدار جدید را در مرز دوره اعمال می‌کند؛ در حالت 2 تغییرِ مقایسه می‌تواند همان دوره را مختل کند. عرض گذار به زمان واقعی نوشتن و سخت‌افزار وابسته است. برای مشاهدهٔ گذار، دیباگر را روی خط نوشتن متوقف نکنید. اگر پالس غیرعادی دیده نشد، زمان نوشتن، OC1PE، CEN و تنظیم Trigger را ثبت کنید؛ نتیجهٔ موفقِ اندازه‌گیری از پیش ادعا نشده است.

آزمایش سوم: توقف و بازگشت

lesson_mode=3 خروجی را به GPIO Low تبدیل می‌کند. سپس حالت را صفر کنید تا AF2 و PWM برگردند. این آزمایش را فقط با پروب یا بار کم‌جریان انجام دهید و حالت پایه را در MODER و AFR نیز بررسی کنید.

12. عیب‌یابی بر پایهٔ مسیر واقعی همین برنامه

نشانه مسیر کد و بررسی اقدام
خروجی Low است lesson_mode=3 تابع PWM_StopLow را اجرا و PA6 را GPIO Output می‌کند؛ خطای تایمر نیست. حالت صفر را انتخاب کنید؛ خروج از حالت 3، PWM_RestoreOutput را اجرا می‌کند. در نمایش عادی تا سه ثانیه برای تغییر حالت صبر کنید.
برنامه در Error_Handler مانده اگر lesson_hal_error=1 باشد، بررسی HAL_TIM_PWM_Start در USER CODE 2 یا Restore شکست خورده است؛ در این نسخه کانال غیرREADY باعث HAL_ERROR می‌شود. Call Stack و ChannelState را بررسی کنید. با Reset از ابتدای راه‌اندازی دنبال کنید؛ Start دوبارهٔ کانال BUSY راه‌حل نیست.
توقف پیش از MX_TIM3_Init اگر lesson_hal_error صفر و Call Stack داخل SystemClock_Config باشد، HAL_RCC_OscConfig یا HAL_RCC_ClockConfig مقدار غیرHAL_OK داده است. تغذیه، کریستال، HSE ON، PLL×9 و Flash Latency را بررسی کنید؛ فرض نکنید برنامه پس از خطا خودکار با HSI ادامه می‌دهد.
فرکانس با 1kHz سازگار نیست SystemClock_Config باید HSE=8MHz و APB1=/2 بدهد؛ tim.c باید PSC=71 و ARR=999 داشته باشد. مقادیر واقعی RCC و رجیسترهای تایمر را بخوانید و با اسکریپت محاسبه کنید. در بستهٔ اصلی همین مقادیر تولید شده‌اند؛ نصف یا دوبرابر شدن را بدون بررسی به HSI نسبت ندهید.
LED روی برد خاموش است ولی PA6 موج دارد USER LED روی PB6 است؛ این نمونه آن پایه را برای PWM راه‌اندازی نمی‌کند. از اسیلوسکوپ یا LED خارجی با مقاومت سری روی PA6 استفاده کنید؛ دو خروجی میکرو را به هم وصل نکنید.
آزمایش Preload جلو نمی‌رود حلقهٔ PWM_PreloadTrial منتظر عبور CNT از 900، مرز دوره و سپس 500 است. CEN و کلاک TIM3 و توقف دیباگر را بررسی کنید. این حلقه‌ها مخصوص آزمایش‌اند؛ برای برنامهٔ تولیدی باید Timeout و زمان‌بندی غیرمسدودکننده طراحی شود.

13. پرسش‌های خودآزمایی با پاسخ تشریحی

پرسش 1: چرا در فرمول تعیین دوره تناوب و فرکانس تایمر در حالت Upcounting، به مقادیر PSC و ARR یک واحد اضافه می‌شود؟

پاسخ تشریحی: در مدارهای منطقی دیجیتال سنکرون، شمارنده از حالت صفر آغاز به کار می‌کند. بنابراین برای پیمودن مسیر از 0 تا یک عدد N، دقیقاً N + 1 پالس ساعت مصرف می‌شود. در بخش تقسیم‌کننده کلاک نیز اگر PSC = 0 باشد، کلاک ورودی مستقیماً و با تقسیم بر 1 عبور داده می‌شود؛ لذا ضریب تقسیم همیشه برابر با PSC + 1 خواهد بود.

پرسش 2: آیا با تایمر 16 بیتی TIM3 و در فرکانس کلاک ورودی 72 مگاهرتز، می‌توان سیگنال PWM با فرکانس 10 هرتز و تفکیک‌پذیری کامل 16 بیت (65,536 سطح) تولید کرد؟

پاسخ تشریحی: خیر. برای تفکیک‌پذیری کامل 16 بیت، باید تعداد بازه‌ها دقیقاً ARR + 1 = 65,536 باشد (ARR = 65,535). در این صورت فرکانس کلاک مورد نیاز شمارنده برای رسیدن به فرکانس 10 هرتز برابر است با:

fCNT = fPWM × (ARR + 1) = 10 × 65,536 = 655,360 Hz

برای تولید این کلاک از منبع 72 مگاهرتزی، ضریب تقسیم مورد نیاز خواهد بود:

PSC + 1 = 72,000,000 / 655,360 ≈ 109.863

از آنجا که PSC تنها می‌تواند یک عدد صحیح باشد، عدد 109 یا 110 خطای فرکانسی ایجاد کرده و فرکانس دقیقاً 10 هرتز به دست نمی‌آید. اگر فرکانس دقیق 10 هرتز مد نظر باشد، ضرایب صحیح به عنوان مثال PSC + 1 = 144 (PSC = 143) کلاک شمارنده را به 500 کیلوهرتز می‌رساند و آنگاه ARR + 1 = 50,000 (ARR = 49,999) خواهد شد که تفکیک‌پذیری آن برابر با 50,000 سطح (حدود 15٫6 بیت) است و نه 16 بیت کامل.

پرسش 3: در حالت PWM Mode 1 با قطبیت فعال بالا، اگر نرم‌افزار به صورت تصادفی مقدار CCR1 را بزرگ‌تر از ARR قرار دهد، شکل‌موج خروجی چه خواهد شد؟

پاسخ تشریحی: در این وضعیت خروجی به صورت دائم و پیوسته در سطح منطقی بالا (3٫3 ولت معادل Duty Cycle = 100%) باقی خواهد ماند؛ زیرا شمارنده حداکثر تا مقدار ARR صعود می‌کند و مجدداً صفر می‌شود، بنابراین شرط CNT < CCR1 همواره در طول تمام چرخه‌ها برقرار می‌ماند.

پرسش 4: تفاوت الکتریکی و عملکردی بین دو تابع HAL_TIM_PWM_Stop و دستور اجباری صفر کردن CCR1 چیست؟

پاسخ تشریحی: در تنظیم صعودی، PWM1 و قطبیت فعال‌بالای این پروژه، CCR1=0 پس از اعمال مقدار بافرشده خروجی فعال را Low می‌کند و تایمر همچنان کار می‌کند. HAL_TIM_PWM_Stop بیت CC1E را غیرفعال می‌کند، اما تنظیم GPIO را تغییر نمی‌دهد؛ صرف فراخوانی Stop برای اثبات سطح Low رانده‌شده کافی نیست. در حالت 3 این پروژه، پایه صریحاً به GPIO Output Push-Pull با مقدار Low تغییر می‌کند.

14. تمرین‌های تکمیلی و مهندسی معکوس

تمرین 1: طراحی PWM فرکانس 20 کیلوهرتز با حداکثر تفکیک‌پذیری سخت‌افزاری

در این تمرین، فرکانس هدف را 20 کیلوهرتز انتخاب کنید و خروجی را فقط با ابزار اندازه‌گیری یا بار کم‌جریان بررسی کنید؛ انتخاب فرکانس برای درایو موتور به مدار قدرت و مشخصات آن وابسته است.

  • برای دستیابی به حداکثر تفکیک‌پذیری، پیش‌تقسیم‌کننده کلاک را برابر صفر قرار دهید (PSC = 0 ⇒ PSC + 1 = 1).
  • مقدار مورد نیاز برای رجیستر دوره تناوب:
    ARR + 1 = 72,000,000 / 20,000 = 3,600 ⇒ ARR = 3,599
  • تعداد بازه‌های گسسته برابر با 3,600 پله است که تفکیک‌پذیری دقیق آن به صورت زیر به دست می‌آید:
    Resolution = log2(3,600) ≈ 11.814 bits

تمرین 2: پیاده‌سازی اثر تنفس LED (Breathing LED) با اصلاح غیرخطی گاما

روشنایی ادراک‌شده الزاماً با نسبت وظیفه خطی نیست. برای تمرین یک نگاشت توانی با گامای پیشنهادی 2٫2 انتخاب کنید؛ این مقدار ثابت جهانی چشم یا LED نیست و باید با هدف نمایش ارزیابی شود.

برای مقایسهٔ تغییر روشنایی با نگاشت خطی، از رابطه اصلاح گاما با ضریب آزمایشی γ = 2.2 استفاده نمایید:

CCR1(step) = round((ARR + 1) × (step / max_steps)2.2)

یک آرایه جدول ارجاع (Lookup Table) با 32 پله محاسباتی در حافظه فلش ایجاد کرده و در فواصل زمانی 20 میلی‌ثانیه، مقادیر آن را در ثبات CCR1 بارگذاری کنید. اثر آن را با LED خارجی و مقاومت سری روی PA6 بررسی کنید؛ برای اتصال به LED داخلی، نخست نقش PB6 و تداخل احتمالی درایورها باید جدا بررسی شود.

تمرین 3: همگام‌سازی دو کانال مستقل خروجی هم‌فرکانس روی تایمر 3 (PA7 / TIM3_CH2)

کانال دوم تایمر 3 بر روی پین PA7 در نگاشت عملکرد جایگزین AF2 در دسترس است. از آنجا که هر دو کانال 1 و 2 در یک ماژول تایمر سخت‌افزاری مشترک قرار دارند، مبنای زمانی شمارنده (TIM3_CNT)، تقسیم‌کننده فرکانس (TIM3_PSC) و سقف دوره تناوب (TIM3_ARR) میان آن‌ها کاملاً مشترک است؛ بنابراین فرکانس خروجی هر دو پین همواره دقیقاً یکسان بوده و لبه‌های آغازین دوره تناوب با یکدیگر همگام هستند.

با این وجود، هر کانال دارای رجیستر مقایسه و مدار مقایسه‌گر سخت‌افزاری مجزا است (TIM3_CCR1 برای PA6 و TIM3_CCR2 برای PA7). در پیکربندی پروژه، کانال 2 را در وضعیت PWM Generation CH2 فعال کرده و در کدهای کاربر، نسبت وظیفه کانال 1 را روی 25٪ (CCR1 = 250) و کانال 2 را روی 75٪ (CCR2 = 750) مقداردهی نمایید. با اتصال دو کانال ورودی اسیلوسکوپ به پایه‌های PA6 و PA7، هم‌فاز بودن لبه‌های صعودی و استقلال کامل پهنای پالس‌ها را بررسی کنید.

15. جدول تطبیق ادعاها و مراجع رسمی سازنده

به منظور ارجاع‌پذیری مهندسی و تضمین انطباق با مستندات کارخانه سازنده (STMicroelectronics)، اطلاعات فنی این درس از مراجع زیر استخراج شده‌اند:

ادعا یا تنظیم مرجع و محل تطبیق
کلاک TIM3 و ضریب APB RM0316، بخش RCC و نمودار درخت کلاک؛ SystemClock_Config واقعی در Core/Src/main.c
شمارش، PWM1، CCR و Preload RM0316، بخش General-purpose timers (TIM2/TIM3/TIM4)، Time-base unit، PWM mode و رجیسترهای CR1/CCMR1/EGR/PSC/ARR/CCR
نگاشت PA6، AF2 و پایهٔ 16 DS9118، جدول Alternate functions for port A و Pin definitions برای LQFP48
PB6، HSE و تغذیهٔ برد شماتیک GB100ST V2.0، IC1، USER LED، MCU 8MHz CRYSTAL و POWER
رفتار Start و Stop فایل همراه پروژه Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_tim.c، توابع HAL_TIM_PWM_Start و HAL_TIM_PWM_Stop؛ commit رسمی 953955afe65f89e60e556bbcdba752597f5da65d
فعال شدن OC1PE هنگام پیکربندی همان فایل، HAL_TIM_PWM_ConfigChannel، شاخهٔ TIM_CHANNEL_1؛ تطابق کامل متن فایل با commit بسته بررسی شد.
نوشتن CCR و غیرفعال شدن تایمر stm32f3xx_hal_tim.h، ماکروهای __HAL_TIM_SET_COMPARE و __HAL_TIM_DISABLE در نسخهٔ همراه پروژه
الگوهای تایمر و راهنمای HAL AN4013 و AN4776 برای توضیح مکمل؛ UM1786 برای ساختار API، با تقدم کد همان نسخه هنگام اختلاف
محدودیت‌های سیلیکون ES0204 Rev11؛ بخش 2.8.1 دربارهٔ BRK2 در TIM1/TIM8 است و نمونهٔ حاضر TIM3 است. REV_ID برد واقعی هنوز خوانده نشده است.
منبع آموزشی کتاب Mastering STM32، ویرایش دوم، Carmine Noviello؛ مبنای ترتیب درس در سند سفارش. متن کامل کتاب در این بازنویسی در دسترس نبوده و نقل از فصل یا صفحهٔ دیده‌نشده انجام نشده است.

اسناد مرجع رسمی و پیوندهای مستقیم:

مطالعه تکمیلی:

برای بررسی سرفصل‌های آموزشی مبتنی بر درایورهای HAL و زنجیره ابزار STM32CubeIDE، کتاب Mastering STM32 (Second Edition) نوشته Carmine Noviello به عنوان راهنمای ساختار مفاهیم پیشنهاد می‌شود.

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

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

لینک‌های فایل‌ها به نسخهٔ مشخص همراه این مقاله متصل‌اند. پروژه با CMake/GCC کامپایل شده است؛ ساخت در CubeIDE و آزمایش روی برد و اسیلوسکوپ هنوز تأیید نشده‌اند.

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

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

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